From a70be6524bce154709da59a0189cfb29f86bfb89 Mon Sep 17 00:00:00 2001 From: Sriram Kiran Senthilkumar Date: Thu, 17 Dec 2020 12:51:46 +0530 Subject: [PATCH] committed the release source --- README.md | 38 +- .../example/lib/main.dart | 1 + .../lib/barcodes.dart | 46 +- .../base/barcode_generator.dart | 97 +- .../base/symbology_base.dart | 116 - .../common/barcode_renderer.dart | 34 +- .../one_dimensional/codabar_symbology.dart | 135 +- .../one_dimensional/code128_symbology.dart | 678 +- .../one_dimensional/code128a_symbology.dart | 12 +- .../one_dimensional/code128b_symbology.dart | 12 +- .../one_dimensional/code128c_symbology.dart | 12 +- .../code39_extended_symbology.dart | 162 +- .../one_dimensional/code39_symbology.dart | 190 +- .../one_dimensional/code93_symbology.dart | 247 +- .../one_dimensional/ean13_symbology.dart | 295 +- .../one_dimensional/ean8_symbology.dart | 234 +- .../one_dimensional/upca_symbology.dart | 299 +- .../one_dimensional/upce_symbology.dart | 311 +- .../one_dimensional/codabar_renderer.dart | 143 + .../one_dimensional/code128_renderer.dart | 689 ++ .../one_dimensional/code128a_renderer.dart | 18 + .../one_dimensional/code128b_renderer.dart | 18 + .../one_dimensional/code128c_renderer.dart | 18 + .../code39_extended_renderer.dart | 166 + .../one_dimensional/code39_renderer.dart | 200 + .../one_dimensional/code93_renderer.dart | 255 + .../one_dimensional/ean13_renderer.dart | 305 + .../one_dimensional/ean8_renderer.dart | 244 + .../symbology_base_renderer.dart | 139 + .../one_dimensional/upca_renderer.dart | 308 + .../one_dimensional/upce_renderer.dart | 320 + .../two_dimensional/datamatrix_renderer.dart | 1173 +++ .../error_correction_codewords.dart | 35 +- .../two_dimensional/qr_code_renderer.dart | 1963 +++++ .../two_dimensional/qr_code_values.dart | 99 +- .../two_dimensional/datamatrix_symbology.dart | 1158 +-- .../two_dimensional/qr_code_symbology.dart | 1947 +---- .../lib/src/barcode_generator/utils/enum.dart | 2 - .../src/barcode_generator/utils/helper.dart | 18 +- .../syncfusion_flutter_calendar/CHANGELOG.md | 24 + .../syncfusion_flutter_calendar/README.md | 18 +- .../lib/calendar.dart | 2 + .../appointment_helper.dart | 116 +- .../month_appointment_helper.dart | 125 +- .../agenda_view_layout.dart | 945 ++- .../allday_appointment_layout.dart | 1254 ++- .../appointment_layout.dart | 2255 +++-- .../calendar/common/calendar_view_helper.dart | 88 +- .../lib/src/calendar/common/enums.dart | 8 + .../lib/src/calendar/common/event_args.dart | 50 + .../calendar/resource_view/resource_view.dart | 32 +- .../scroll_view/custom_scroll_view.dart | 677 +- .../lib/src/calendar/sfcalendar.dart | 1533 ++-- .../lib/src/calendar/views/calendar_view.dart | 701 +- .../views/custom_calendar_button.dart | 239 + .../lib/src/calendar/views/day_view.dart | 731 +- .../lib/src/calendar/views/header_view.dart | 960 +-- .../lib/src/calendar/views/month_view.dart | 68 +- .../calendar/views/multi_child_container.dart | 193 + .../lib/src/calendar/views/schedule_view.dart | 29 +- .../src/calendar/views/selection_view.dart | 20 +- .../src/calendar/views/time_ruler_view.dart | 4 + .../lib/src/calendar/views/timeline_view.dart | 871 +- .../syncfusion_flutter_calendar/pubspec.yaml | 2 +- .../syncfusion_flutter_charts/CHANGELOG.md | 72 + packages/syncfusion_flutter_charts/README.md | 142 +- .../example/lib/main.dart | 68 +- .../syncfusion_flutter_charts/lib/charts.dart | 1 + .../lib/sparkcharts.dart | 12 + .../lib/src/chart/axis/axis.dart | 179 +- .../lib/src/chart/axis/axis_panel.dart | 5 +- .../lib/src/chart/axis/category_axis.dart | 29 +- .../lib/src/chart/axis/datetime_axis.dart | 175 +- .../lib/src/chart/axis/logarithmic_axis.dart | 153 +- .../lib/src/chart/axis/numeric_axis.dart | 27 +- .../lib/src/chart/base/chart_base.dart | 581 +- .../lib/src/chart/base/series_base.dart | 55 +- .../src/chart/chart_segment/line_segment.dart | 62 +- .../chart/chart_segment/stepline_segment.dart | 33 +- .../chart/chart_series/fastline_series.dart | 4 + .../chart_series/range_column_series.dart | 1 + .../chart/chart_series/scatter_series.dart | 2 +- .../lib/src/chart/chart_series/series.dart | 204 +- .../chart/chart_series/stepline_series.dart | 6 +- .../chart/chart_series/xy_data_series.dart | 42 +- .../lib/src/chart/common/common.dart | 88 +- .../lib/src/chart/common/data_label.dart | 3 + .../src/chart/common/data_label_renderer.dart | 49 +- .../lib/src/chart/common/marker.dart | 63 +- .../lib/src/chart/common/renderer.dart | 171 +- .../chart/series_painter/area_painter.dart | 33 +- .../src/chart/series_painter/bar_painter.dart | 4 + .../box_and_whisker_painter.dart | 8 +- .../chart/series_painter/bubble_painter.dart | 4 + .../chart/series_painter/candle_painter.dart | 9 +- .../chart/series_painter/column_painter.dart | 4 + .../series_painter/fastline_painter.dart | 4 + .../chart/series_painter/hilo_painter.dart | 8 +- .../series_painter/hiloopenclose_painter.dart | 9 +- .../series_painter/histogram_painter.dart | 16 +- .../chart/series_painter/line_painter.dart | 4 + .../series_painter/range_area_painter.dart | 146 +- .../series_painter/range_column_painter.dart | 4 + .../chart/series_painter/scatter_painter.dart | 4 + .../series_painter/spline_area_painter.dart | 127 +- .../chart/series_painter/spline_painter.dart | 9 +- .../spline_range_area_painter.dart | 335 +- .../series_painter/step_area_painter.dart | 79 +- .../series_painter/stepline_painter.dart | 5 + .../series_painter/waterfall_painter.dart | 4 + .../technical_indicator.dart | 4 +- .../chart/trendlines/trendlines_painter.dart | 10 +- .../src/chart/user_interaction/crosshair.dart | 52 +- .../user_interaction/crosshair_painter.dart | 28 +- .../user_interaction/selection_renderer.dart | 128 +- .../user_interaction/tooltip_painter.dart | 394 +- .../user_interaction/tooltip_template.dart | 31 +- .../src/chart/user_interaction/trackball.dart | 1385 ++- .../user_interaction/trackball_painter.dart | 1283 +-- .../user_interaction/trackball_template.dart | 533 ++ .../user_interaction/zooming_panning.dart | 28 +- .../lib/src/chart/utils/helper.dart | 149 +- .../circular_chart/base/circular_base.dart | 88 +- .../src/circular_chart/base/series_base.dart | 12 + .../renderer/circular_series.dart | 5 +- .../renderer/data_label_renderer.dart | 18 +- .../lib/src/circular_chart/utils/helper.dart | 20 + .../lib/src/common/common.dart | 3 + .../lib/src/common/event_args.dart | 352 +- .../lib/src/common/legend/legend.dart | 30 +- .../lib/src/common/legend/renderer.dart | 2 +- .../lib/src/common/template/rendering.dart | 341 +- .../src/common/user_interaction/tooltip.dart | 318 +- .../lib/src/common/utils/helper.dart | 10 +- .../src/funnel_chart/base/funnel_base.dart | 54 +- .../src/funnel_chart/base/series_base.dart | 20 +- .../renderer/data_label_renderer.dart | 19 +- .../src/pyramid_chart/base/pyramid_base.dart | 54 +- .../src/pyramid_chart/base/series_base.dart | 20 +- .../renderer/data_label_renderer.dart | 19 +- .../lib/src/sparkline/marker.dart | 64 + .../lib/src/sparkline/plot_band.dart | 136 + .../sparkline/renderers/renderer_base.dart | 712 ++ .../renderers/spark_area_renderer.dart | 458 + .../renderers/spark_bar_renderer.dart | 461 + .../renderers/spark_line_renderer.dart | 447 + .../renderers/spark_win_loss_renderer.dart | 327 + .../src/sparkline/series/spark_area_base.dart | 513 ++ .../src/sparkline/series/spark_bar_base.dart | 476 ++ .../src/sparkline/series/spark_line_base.dart | 512 ++ .../sparkline/series/spark_win_loss_base.dart | 436 + .../trackball/spark_chart_trackball.dart | 289 + .../trackball/trackball_renderer.dart | 487 ++ .../lib/src/sparkline/utils/enum.dart | 93 + .../lib/src/sparkline/utils/helper.dart | 812 ++ .../syncfusion_flutter_charts/pubspec.yaml | 2 +- .../syncfusion_flutter_core/lib/core.dart | 1 + .../lib/src/calendar/calendar_helper.dart | 70 +- .../src/calendar/custom_looping_widget.dart | 64 +- .../lib/src/calendar/hijri_date_time.dart | 2056 +++++ .../localizations/global_localizations.dart | 155 +- .../lib/src/slider_controller.dart | 10 +- .../lib/src/theme/datagrid_theme.dart | 42 +- .../lib/src/theme/datapager_theme.dart | 10 +- .../lib/src/theme/maps_theme.dart | 33 - .../syncfusion_flutter_datagrid/CHANGELOG.md | 46 +- .../syncfusion_flutter_datagrid/README.md | 4 + .../lib/datagrid.dart | 7 +- .../grid_cell_stacked_header_renderer.dart | 46 + .../grid_cell_widget_renderer.dart | 1 + .../grid_header_cell_renderer.dart | 35 +- .../grid_virtualizing_cell_renderer_base.dart | 26 +- .../cell_control/grid_cell_widget.dart | 464 +- .../cell_control/grid_header_cell_widget.dart | 87 +- .../virtualizing_cells_widget.dart | 113 +- .../lib/src/control/datagrid_datasource.dart | 167 +- .../src/control/generator/data_cell_base.dart | 11 + .../lib/src/control/generator/data_row.dart | 20 +- .../src/control/generator/data_row_base.dart | 30 +- .../src/control/generator/row_generator.dart | 48 +- .../control/generator/spanned_data_row.dart | 165 + .../lib/src/control/helper/enums.dart | 6 + .../control/helper/grid_index_resolver.dart | 106 +- .../lib/src/control/runtime/column_sizer.dart | 76 +- .../lib/src/control/runtime/grid_column.dart | 324 +- .../src/control/runtime/stacked_header.dart | 53 + .../scrollable_panel/scrollview_widget.dart | 102 +- .../visual_container_helper.dart | 10 +- .../current_cell_manager.dart | 2 +- .../row_selection_manager.dart | 4 +- .../selection_manager_base.dart | 5 +- .../lib/src/datapager/sfdatapager.dart | 196 +- .../collections/generic_tree_table.dart | 411 - .../generic_tree_table_with_counter.dart | 535 -- .../generic_tree_table_with_summary.dart | 416 - .../grid_common/collections/tree_table.dart | 14 - .../src/grid_common/list_generic_base.dart | 73 - .../distance_counter_subset.dart | 3 +- .../lib/src/sfdatagrid.dart | 348 +- .../CHANGELOG.md | 17 +- .../syncfusion_flutter_datepicker/README.md | 12 + .../analysis_options.yaml | 3 +- .../lib/datepicker.dart | 48 +- .../common/date_picker_helper.dart | 133 - .../date_picker/common/date_time_helper.dart | 439 - .../lib/src/date_picker/date_picker.dart | 7533 +++++++++++++++-- ...settings.dart => date_picker_manager.dart} | 811 +- .../header_view/picker_header_painter.dart | 236 - .../header_view/picker_header_view.dart | 292 - .../header_view/picker_view_header.dart | 207 - .../hijri_date_picker_manager.dart | 1715 ++++ .../looping_widget/picker_scroll_view.dart | 1838 ---- .../lib/src/date_picker/month_view.dart | 3638 ++++++++ .../src/date_picker/month_view/helper.dart | 732 -- .../src/date_picker/month_view/interface.dart | 59 - .../month_view/multi_range_selection.dart | 263 - .../month_view/multi_selection.dart | 201 - .../month_view/range_selection.dart | 244 - .../month_view/single_selection.dart | 197 - .../src/date_picker/picker_controller.dart | 636 -- .../lib/src/date_picker/picker_helper.dart | 839 ++ .../lib/src/date_picker/picker_view.dart | 1515 ---- .../lib/src/date_picker/year_view.dart | 3324 ++++++++ .../date_picker/year_view/century_view.dart | 194 - .../date_picker/year_view/decade_view.dart | 193 - .../lib/src/date_picker/year_view/helper.dart | 812 -- .../src/date_picker/year_view/interface.dart | 83 - .../src/date_picker/year_view/year_view.dart | 203 - .../annotation/gauge_annotation.dart | 12 +- .../lib/src/radial_gauge/axis/gauge_axis.dart | 50 +- .../src/radial_gauge/axis/radial_axis.dart | 8 +- .../lib/src/radial_gauge/common/common.dart | 84 +- .../common/radial_gauge_renderer.dart | 41 +- .../src/radial_gauge/gauge/radial_gauge.dart | 2 +- .../radial_gauge/pointers/gauge_pointer.dart | 11 +- .../radial_gauge/pointers/marker_pointer.dart | 10 +- .../radial_gauge/pointers/needle_pointer.dart | 6 +- .../radial_gauge/pointers/range_pointer.dart | 14 +- .../src/radial_gauge/range/gauge_range.dart | 22 +- packages/syncfusion_flutter_maps/README.md | 79 +- .../example/lib/main.dart | 19 +- .../syncfusion_flutter_maps/lib/maps.dart | 475 +- .../zoom_pan_behavior.dart | 163 +- .../lib/src/common/maps_shapes.dart | 301 - .../lib/src/common/settings.dart | 2737 ------ .../lib/src/common/utils.dart | 287 - .../default_controller.dart | 106 +- .../controller/shape_layer_controller.dart | 72 + .../maps_bubble.dart => elements/bubble.dart} | 389 +- .../data_label.dart} | 174 +- .../lib/src/elements/legend.dart | 2398 ++++++ .../maps_marker.dart => elements/marker.dart} | 320 +- .../lib/src/elements/shapes.dart | 212 + .../lib/src/elements/toolbar.dart | 285 + .../lib/src/elements/tooltip.dart | 678 ++ .../lib/src/{common => }/enum.dart | 61 +- .../lib/src/features/maps_legend.dart | 449 - .../lib/src/features/maps_toolbar.dart | 242 - .../lib/src/features/maps_tooltip.dart | 426 - .../lib/src/layer/layer_base.dart | 467 + .../lib/src/layer/maps_layer.dart | 1595 ---- .../lib/src/layer/shape_layer.dart | 4374 ++++++++++ .../lib/src/layer/shape_layer_controller.dart | 187 - .../lib/src/layer/shape_layer_render_box.dart | 1504 ---- .../{maps_tile_layer.dart => tile_layer.dart} | 1001 ++- .../lib/src/layer/vector_layers.dart | 5436 ++++++++++++ .../syncfusion_flutter_maps/lib/src/maps.dart | 269 - .../lib/src/settings.dart | 1357 +++ .../lib/src/utils.dart | 129 + packages/syncfusion_flutter_pdf/CHANGELOG.md | 73 +- packages/syncfusion_flutter_pdf/README.md | 336 +- packages/syncfusion_flutter_pdf/lib/pdf.dart | 23 + .../annotations/pdf_annotation.dart | 17 + .../pdf_document_link_annotation.dart | 65 +- .../color_space/pdf_icc_color_profile.dart | 38 + .../pdf_text_extractor/font_structure.dart | 290 +- .../pdf_text_extractor/image_renderer.dart | 266 +- .../pdf_text_extractor.dart | 524 +- .../implementation/general/embedded_file.dart | 78 + .../general/embedded_file_params.dart | 46 + .../general/embedded_file_specification.dart | 56 + .../general/file_specification_base.dart | 62 + .../graphics/figures/base/layout_element.dart | 5 +- .../figures/base/pdf_shape_element.dart | 7 +- .../graphics/figures/base/shape_layouter.dart | 41 +- .../graphics/figures/pdf_bezier_curve.dart | 9 +- .../graphics/figures/pdf_path.dart | 5 +- .../graphics/figures/pdf_template.dart | 57 +- .../fonts/unicode_true_type_font.dart | 51 + .../graphics/images/pdf_bitmap.dart | 12 + .../graphics/images/pdf_image.dart | 2 +- .../implementation/graphics/pdf_graphics.dart | 169 +- .../graphics/pdf_resources.dart | 1 + .../graphics/pdf_transparency.dart | 11 +- .../pdf/implementation/io/cross_table.dart | 13 + .../io/dictionary_properties.dart | 53 +- .../implementation/io/pdf_cross_table.dart | 128 +- .../src/pdf/implementation/io/pdf_parser.dart | 8 +- .../implementation/io/pdf_stream_writer.dart | 2 +- .../pdf/implementation/pages/pdf_layer.dart | 449 + .../pages/pdf_layer_collection.dart | 763 ++ .../pdf/implementation/pages/pdf_page.dart | 29 +- .../implementation/pages/pdf_page_layer.dart | 145 +- .../pages/pdf_page_layer_collection.dart | 578 +- .../pages/pdf_page_template_element.dart | 1 + .../attachments/pdf_attachment.dart | 83 + .../pdf_attachment_collection.dart | 372 + .../implementation/pdf_document/enums.dart | 39 + .../pdf_document/outlines/pdf_outline.dart | 2 + .../pdf_document/pdf_catalog.dart | 73 + .../pdf_document/pdf_catalog_names.dart | 152 + .../pdf_document/pdf_document.dart | 370 +- .../pdf_document_information.dart | 240 + .../implementation/primitives/pdf_array.dart | 18 + .../primitives/pdf_boolean.dart | 3 + .../primitives/pdf_dictionary.dart | 100 +- .../implementation/primitives/pdf_name.dart | 3 + .../implementation/primitives/pdf_null.dart | 3 + .../implementation/primitives/pdf_number.dart | 3 + .../primitives/pdf_reference.dart | 3 + .../primitives/pdf_reference_holder.dart | 60 +- .../implementation/primitives/pdf_stream.dart | 60 +- .../implementation/primitives/pdf_string.dart | 170 +- .../cryptography/aes_cipher.dart | 1165 +++ .../cryptography/aes_engine.dart | 2974 +++++++ .../cipher_block_chaining_mode.dart | 138 + .../src/pdf/implementation/security/enum.dart | 61 + .../security/pdf_encryptor.dart | 1409 +++ .../implementation/security/pdf_security.dart | 443 + .../structured_elements/grid/pdf_grid.dart | 51 - .../pdf/implementation/xmp/xmp_metadata.dart | 337 + .../lib/src/pdf/interfaces/pdf_primitive.dart | 2 + packages/syncfusion_flutter_pdf/pubspec.yaml | 3 +- .../syncfusion_flutter_pdfviewer/CHANGELOG.md | 23 + .../syncfusion_flutter_pdfviewer/README.md | 2 +- .../SyncfusionFlutterPdfViewerPlugin.java | 33 +- ...wiftSyncfusionFlutterPdfViewerPlugin.swift | 12 +- .../lib/src/bookmark/bookmark_view.dart | 1 + .../lib/src/common/pdf_provider.dart | 12 +- .../lib/src/common/pdfviewer_helper.dart | 48 + .../lib/src/common/pdfviewer_plugin.dart | 10 +- .../lib/src/control/pdf_container.dart | 34 +- .../lib/src/control/pdf_page_view.dart | 160 +- .../control/pdfviewer_callback_details.dart | 27 + .../lib/src/control/pdfviewer_canvas.dart | 1001 +++ .../lib/src/control/scroll_head.dart | 253 +- .../lib/src/control/scroll_head_overlay.dart | 222 + .../lib/src/pdfviewer.dart | 1063 ++- .../syncfusion_flutter_pdfviewer/pubspec.yaml | 1 + .../CHANGELOG.md | 2 +- .../syncfusion_flutter_signaturepad/README.md | 20 +- packages/syncfusion_flutter_sliders/README.md | 4 +- .../example/android/.gitignore | 7 + .../example/ios/.gitignore | 32 + .../example/lib/main.dart | 59 +- .../lib/sliders.dart | 38 +- .../lib/src/common.dart | 162 +- .../lib/src/constants.dart | 43 + .../lib/src/range_selector.dart | 338 +- .../lib/src/range_slider.dart | 255 +- .../lib/src/render_slider_base.dart | 78 +- .../lib/src/slider.dart | 220 +- .../lib/src/slider_shapes.dart | 182 +- .../syncfusion_flutter_xlsio/CHANGELOG.md | 18 + packages/syncfusion_flutter_xlsio/README.md | 240 +- .../example/lib/main.dart | 4 +- .../lib/src/xlsio/calculate/calc_engine.dart | 932 +- .../lib/src/xlsio/cell_styles/cell_style.dart | 13 +- .../xlsio/cell_styles/cell_style_wrapper.dart | 36 +- .../src/xlsio/cell_styles/cell_style_xfs.dart | 3 + .../lib/src/xlsio/cell_styles/font.dart | 10 +- .../lib/src/xlsio/cell_styles/style.dart | 5 +- .../xlsio/cell_styles/styles_collection.dart | 5 + .../{_constants.dart => constants.dart} | 0 .../format_tokens/decimal_point_token.dart | 2 +- .../formats/format_tokens/digit_token.dart | 41 - .../format_tokens/{_enums.dart => enums.dart} | 0 ...token_base.dart => format_token_base.dart} | 0 .../formats/format_tokens/fraction_token.dart | 14 +- .../format_tokens/milli_second_token.dart | 2 +- .../significant_digit_token.dart | 23 +- .../format_tokens/single_char_token.dart | 39 - .../src/xlsio/general/autofit_manager.dart | 698 ++ .../lib/src/xlsio/general/enums.dart | 125 +- .../src/xlsio/general/serialize_workbook.dart | 402 +- .../lib/src/xlsio/general/workbook.dart | 6363 +++++++++++++- .../lib/src/xlsio/hyperlinks/hyperlink.dart | 51 + .../hyperlinks/hyperlink_collection.dart | 67 + .../lib/src/xlsio/images/picture.dart | 103 + .../src/xlsio/images/pictures_collection.dart | 33 +- .../merged_cells/merged_cell_collection.dart | 8 +- .../lib/src/xlsio/range/range.dart | 222 +- .../lib/src/xlsio/range/range_collection.dart | 12 + .../lib/src/xlsio/range/row_collection.dart | 12 + .../security/excel_sheet_protection.dart | 59 + .../src/xlsio/security/security_helper.dart | 38 + .../lib/src/xlsio/worksheet/worksheet.dart | 1082 ++- .../syncfusion_flutter_xlsio/lib/xlsio.dart | 18 +- .../syncfusion_flutter_xlsio/pubspec.yaml | 1 + .../generated_syncfusion_localizations.dart | 5711 ++++++++++++- .../lib/src/l10n/syncfusion_af.arb | 26 +- .../lib/src/l10n/syncfusion_am.arb | 26 +- .../lib/src/l10n/syncfusion_ar.arb | 28 +- .../lib/src/l10n/syncfusion_az.arb | 26 +- .../lib/src/l10n/syncfusion_be.arb | 28 +- .../lib/src/l10n/syncfusion_bg.arb | 26 +- .../lib/src/l10n/syncfusion_bn.arb | 28 +- .../lib/src/l10n/syncfusion_bs.arb | 26 +- .../lib/src/l10n/syncfusion_ca.arb | 26 +- .../lib/src/l10n/syncfusion_cs.arb | 26 +- .../lib/src/l10n/syncfusion_da.arb | 30 +- .../lib/src/l10n/syncfusion_de.arb | 26 +- .../lib/src/l10n/syncfusion_el.arb | 26 +- .../lib/src/l10n/syncfusion_en.arb | 190 +- .../lib/src/l10n/syncfusion_en.json | 26 +- .../lib/src/l10n/syncfusion_es.arb | 26 +- .../lib/src/l10n/syncfusion_et.arb | 26 +- .../lib/src/l10n/syncfusion_eu.arb | 28 +- .../lib/src/l10n/syncfusion_fa.arb | 26 +- .../lib/src/l10n/syncfusion_fi.arb | 28 +- .../lib/src/l10n/syncfusion_fil.arb | 26 +- .../lib/src/l10n/syncfusion_fr.arb | 26 +- .../lib/src/l10n/syncfusion_gl.arb | 26 +- .../lib/src/l10n/syncfusion_gu.arb | 26 +- .../lib/src/l10n/syncfusion_he.arb | 26 +- .../lib/src/l10n/syncfusion_hi.arb | 28 +- .../lib/src/l10n/syncfusion_hr.arb | 28 +- .../lib/src/l10n/syncfusion_hu.arb | 28 +- .../lib/src/l10n/syncfusion_hy.arb | 28 +- .../lib/src/l10n/syncfusion_id.arb | 26 +- .../lib/src/l10n/syncfusion_is.arb | 30 +- .../lib/src/l10n/syncfusion_it.arb | 28 +- .../lib/src/l10n/syncfusion_ja.arb | 36 +- .../lib/src/l10n/syncfusion_ka.arb | 32 +- .../lib/src/l10n/syncfusion_kk.arb | 26 +- .../lib/src/l10n/syncfusion_km.arb | 26 +- .../lib/src/l10n/syncfusion_kn.arb | 26 +- .../lib/src/l10n/syncfusion_ko.arb | 26 +- .../lib/src/l10n/syncfusion_ky.arb | 28 +- .../lib/src/l10n/syncfusion_lo.arb | 26 +- .../lib/src/l10n/syncfusion_lt.arb | 26 +- .../lib/src/l10n/syncfusion_lv.arb | 26 +- .../lib/src/l10n/syncfusion_mk.arb | 26 +- .../lib/src/l10n/syncfusion_ml.arb | 26 +- .../lib/src/l10n/syncfusion_mn.arb | 26 +- .../lib/src/l10n/syncfusion_mr.arb | 28 +- .../lib/src/l10n/syncfusion_ms.arb | 26 +- .../lib/src/l10n/syncfusion_my.arb | 26 +- .../lib/src/l10n/syncfusion_nb.arb | 26 +- .../lib/src/l10n/syncfusion_ne.arb | 26 +- .../lib/src/l10n/syncfusion_nl.arb | 28 +- .../lib/src/l10n/syncfusion_pa.arb | 28 +- .../lib/src/l10n/syncfusion_pl.arb | 30 +- .../lib/src/l10n/syncfusion_ps.arb | 26 +- .../lib/src/l10n/syncfusion_pt.arb | 26 +- .../lib/src/l10n/syncfusion_pt_PT.arb | 26 +- .../lib/src/l10n/syncfusion_ro.arb | 26 +- .../lib/src/l10n/syncfusion_ru.arb | 30 +- .../lib/src/l10n/syncfusion_si.arb | 28 +- .../lib/src/l10n/syncfusion_sk.arb | 28 +- .../lib/src/l10n/syncfusion_sl.arb | 26 +- .../lib/src/l10n/syncfusion_sq.arb | 26 +- .../lib/src/l10n/syncfusion_sr.arb | 30 +- .../lib/src/l10n/syncfusion_sv.arb | 26 +- .../lib/src/l10n/syncfusion_sw.arb | 28 +- .../lib/src/l10n/syncfusion_ta.arb | 26 +- .../lib/src/l10n/syncfusion_te.arb | 26 +- .../lib/src/l10n/syncfusion_th.arb | 28 +- .../lib/src/l10n/syncfusion_tl.arb | 26 +- .../lib/src/l10n/syncfusion_tr.arb | 26 +- .../lib/src/l10n/syncfusion_uk.arb | 30 +- .../lib/src/l10n/syncfusion_ur.arb | 26 +- .../lib/src/l10n/syncfusion_uz.arb | 26 +- .../lib/src/l10n/syncfusion_vi.arb | 30 +- .../lib/src/l10n/syncfusion_zh.arb | 26 +- .../lib/src/l10n/syncfusion_zh_HK.arb | 26 +- .../lib/src/l10n/syncfusion_zh_TW.arb | 26 +- .../lib/src/l10n/syncfusion_zu.arb | 26 +- packages/syncfusion_officechart/CHANGELOG.md | 6 + packages/syncfusion_officechart/README.md | 444 +- .../example/lib/main.dart | 4 +- .../lib/officechart.dart | 2 - .../lib/src/chart/chart_category_axis.dart | 4 + .../lib/src/chart/chart_collection.dart | 5 +- .../lib/src/chart/chart_enum.dart | 32 +- .../lib/src/chart/chart_impl.dart | 101 +- .../lib/src/chart/chart_serialization.dart | 63 +- .../lib/src/chart/chart_serie.dart | 4 +- .../lib/src/chart/chart_text_area.dart | 4 +- .../syncfusion_officecore/example/README.md | 3 + .../example/analysis_options.yaml | 6 + .../example/android/.gitignore | 7 + .../example/android/app/build.gradle | 63 + .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 47 + .../officecore_example/MainActivity.kt | 6 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + .../example/android/build.gradle | 31 + .../example/android/gradle.properties | 4 + .../gradle/wrapper/gradle-wrapper.properties | 6 + .../android/officecore_example_android.iml | 29 + .../example/android/settings.gradle | 15 + .../example/ios/.gitignore | 32 + .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../example/ios/Flutter/Debug.xcconfig | 1 + .../example/ios/Flutter/Release.xcconfig | 1 + .../ios/Runner.xcodeproj/project.pbxproj | 506 ++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 91 + .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 + .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1025 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1716 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 1920 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 1895 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 3831 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 1888 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 3294 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 3612 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../example/ios/Runner/Info.plist | 45 + .../ios/Runner/Runner-Bridging-Header.h | 1 + .../example/{ => lib}/main.dart | 4 +- .../example/officecore_example.iml | 18 + .../example/pubspec.yaml | 19 + 550 files changed, 100931 insertions(+), 33795 deletions(-) create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/codabar_renderer.dart create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128_renderer.dart create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128a_renderer.dart create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128b_renderer.dart create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128c_renderer.dart create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code39_extended_renderer.dart create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code39_renderer.dart create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code93_renderer.dart create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/ean13_renderer.dart create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/ean8_renderer.dart create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/symbology_base_renderer.dart create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/upca_renderer.dart create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/upce_renderer.dart create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/datamatrix_renderer.dart rename packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/{ => renderers}/two_dimensional/error_correction_codewords.dart (97%) create mode 100644 packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/qr_code_renderer.dart rename packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/{ => renderers}/two_dimensional/qr_code_values.dart (97%) create mode 100644 packages/syncfusion_flutter_calendar/lib/src/calendar/views/custom_calendar_button.dart create mode 100644 packages/syncfusion_flutter_calendar/lib/src/calendar/views/multi_child_container.dart create mode 100644 packages/syncfusion_flutter_charts/lib/sparkcharts.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_template.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/marker.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/plot_band.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/renderer_base.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_area_renderer.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_bar_renderer.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_line_renderer.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_win_loss_renderer.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_area_base.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_bar_base.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_line_base.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_win_loss_base.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/spark_chart_trackball.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/trackball_renderer.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/utils/enum.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart create mode 100644 packages/syncfusion_flutter_core/lib/src/calendar/hijri_date_time.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_stacked_header_renderer.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/generator/spanned_data_row.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/runtime/stacked_header.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/generic_tree_table.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/generic_tree_table_with_counter.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/generic_tree_table_with_summary.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/common/date_picker_helper.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/common/date_time_helper.dart rename packages/syncfusion_flutter_datepicker/lib/src/date_picker/{picker_settings.dart => date_picker_manager.dart} (70%) delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/header_view/picker_header_painter.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/header_view/picker_header_view.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/header_view/picker_view_header.dart create mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/hijri_date_picker_manager.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/looping_widget/picker_scroll_view.dart create mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/helper.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/interface.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/multi_range_selection.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/multi_selection.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/range_selection.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/single_selection.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_controller.dart create mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_helper.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_view.dart create mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/century_view.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/decade_view.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/helper.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/interface.dart delete mode 100644 packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/year_view.dart rename packages/syncfusion_flutter_maps/lib/src/{layer => behavior}/zoom_pan_behavior.dart (75%) delete mode 100644 packages/syncfusion_flutter_maps/lib/src/common/maps_shapes.dart delete mode 100644 packages/syncfusion_flutter_maps/lib/src/common/settings.dart delete mode 100644 packages/syncfusion_flutter_maps/lib/src/common/utils.dart rename packages/syncfusion_flutter_maps/lib/src/{layer => controller}/default_controller.dart (68%) create mode 100644 packages/syncfusion_flutter_maps/lib/src/controller/shape_layer_controller.dart rename packages/syncfusion_flutter_maps/lib/src/{features/maps_bubble.dart => elements/bubble.dart} (60%) rename packages/syncfusion_flutter_maps/lib/src/{features/maps_data_label.dart => elements/data_label.dart} (57%) create mode 100644 packages/syncfusion_flutter_maps/lib/src/elements/legend.dart rename packages/syncfusion_flutter_maps/lib/src/{features/maps_marker.dart => elements/marker.dart} (71%) create mode 100644 packages/syncfusion_flutter_maps/lib/src/elements/shapes.dart create mode 100644 packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart create mode 100644 packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart rename packages/syncfusion_flutter_maps/lib/src/{common => }/enum.dart (56%) delete mode 100644 packages/syncfusion_flutter_maps/lib/src/features/maps_legend.dart delete mode 100644 packages/syncfusion_flutter_maps/lib/src/features/maps_toolbar.dart delete mode 100644 packages/syncfusion_flutter_maps/lib/src/features/maps_tooltip.dart create mode 100644 packages/syncfusion_flutter_maps/lib/src/layer/layer_base.dart delete mode 100644 packages/syncfusion_flutter_maps/lib/src/layer/maps_layer.dart create mode 100644 packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart delete mode 100644 packages/syncfusion_flutter_maps/lib/src/layer/shape_layer_controller.dart delete mode 100644 packages/syncfusion_flutter_maps/lib/src/layer/shape_layer_render_box.dart rename packages/syncfusion_flutter_maps/lib/src/layer/{maps_tile_layer.dart => tile_layer.dart} (54%) create mode 100644 packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart delete mode 100644 packages/syncfusion_flutter_maps/lib/src/maps.dart create mode 100644 packages/syncfusion_flutter_maps/lib/src/settings.dart create mode 100644 packages/syncfusion_flutter_maps/lib/src/utils.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/color_space/pdf_icc_color_profile.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_params.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_specification.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/file_specification_base.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer_collection.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment_collection.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_catalog_names.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document_information.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/aes_cipher.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/aes_engine.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/cipher_block_chaining_mode.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/enum.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_encryptor.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_security.dart create mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/xmp/xmp_metadata.dart create mode 100644 packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart create mode 100644 packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart create mode 100644 packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head_overlay.dart create mode 100644 packages/syncfusion_flutter_sliders/example/android/.gitignore create mode 100644 packages/syncfusion_flutter_sliders/example/ios/.gitignore create mode 100644 packages/syncfusion_flutter_sliders/lib/src/constants.dart rename packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/{_constants.dart => constants.dart} (100%) delete mode 100644 packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/digit_token.dart rename packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/{_enums.dart => enums.dart} (100%) rename packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/{_format_token_base.dart => format_token_base.dart} (100%) delete mode 100644 packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/single_char_token.dart create mode 100644 packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/autofit_manager.dart create mode 100644 packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink.dart create mode 100644 packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink_collection.dart create mode 100644 packages/syncfusion_flutter_xlsio/lib/src/xlsio/security/excel_sheet_protection.dart create mode 100644 packages/syncfusion_flutter_xlsio/lib/src/xlsio/security/security_helper.dart create mode 100644 packages/syncfusion_officecore/example/README.md create mode 100644 packages/syncfusion_officecore/example/analysis_options.yaml create mode 100644 packages/syncfusion_officecore/example/android/.gitignore create mode 100644 packages/syncfusion_officecore/example/android/app/build.gradle create mode 100644 packages/syncfusion_officecore/example/android/app/src/debug/AndroidManifest.xml create mode 100644 packages/syncfusion_officecore/example/android/app/src/main/AndroidManifest.xml create mode 100644 packages/syncfusion_officecore/example/android/app/src/main/kotlin/com/example/officecore_example/MainActivity.kt create mode 100644 packages/syncfusion_officecore/example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 packages/syncfusion_officecore/example/android/app/src/main/res/values/styles.xml create mode 100644 packages/syncfusion_officecore/example/android/app/src/profile/AndroidManifest.xml create mode 100644 packages/syncfusion_officecore/example/android/build.gradle create mode 100644 packages/syncfusion_officecore/example/android/gradle.properties create mode 100644 packages/syncfusion_officecore/example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 packages/syncfusion_officecore/example/android/officecore_example_android.iml create mode 100644 packages/syncfusion_officecore/example/android/settings.gradle create mode 100644 packages/syncfusion_officecore/example/ios/.gitignore create mode 100644 packages/syncfusion_officecore/example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 packages/syncfusion_officecore/example/ios/Flutter/Debug.xcconfig create mode 100644 packages/syncfusion_officecore/example/ios/Flutter/Release.xcconfig create mode 100644 packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/syncfusion_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/syncfusion_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/syncfusion_officecore/example/ios/Runner/AppDelegate.swift create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Info.plist create mode 100644 packages/syncfusion_officecore/example/ios/Runner/Runner-Bridging-Header.h rename packages/syncfusion_officecore/example/{ => lib}/main.dart (98%) create mode 100644 packages/syncfusion_officecore/example/officecore_example.iml create mode 100644 packages/syncfusion_officecore/example/pubspec.yaml diff --git a/README.md b/README.md index 4571bdf10..1b82004d0 100644 --- a/README.md +++ b/README.md @@ -30,24 +30,24 @@ Also, you can view the samples code from [this repository](https://github.com/sy ## Packages -|
Package/Plugin
|
Widgets/libraries
|
Pub
|
Points
|
Popularity
| -|----------------|-------------------|-----|--------|------------| -| [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) | -| [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) | -| [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) | -| [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) | -| [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) | -| [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) | -| [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) | -| [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) | -| [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) | -| [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) | -| [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) | -| [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) | -| [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) | -| [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) | -| [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) | -| [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) | +| 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) | ## How to use @@ -141,4 +141,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. +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 diff --git a/packages/syncfusion_flutter_barcodes/example/lib/main.dart b/packages/syncfusion_flutter_barcodes/example/lib/main.dart index 836b6a8d6..8d333b1eb 100644 --- a/packages/syncfusion_flutter_barcodes/example/lib/main.dart +++ b/packages/syncfusion_flutter_barcodes/example/lib/main.dart @@ -5,6 +5,7 @@ void main() { return runApp(MyApp()); } +/// Creates the barcode generator class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { diff --git a/packages/syncfusion_flutter_barcodes/lib/barcodes.dart b/packages/syncfusion_flutter_barcodes/lib/barcodes.dart index 49275b36f..4da70cc7e 100644 --- a/packages/syncfusion_flutter_barcodes/lib/barcodes.dart +++ b/packages/syncfusion_flutter_barcodes/lib/barcodes.dart @@ -11,31 +11,23 @@ /// * [Knowledge base](https://www.syncfusion.com/kb/flutter) library barcodes; -import 'dart:convert' show utf8; -import 'dart:ui'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; - // export barcode library -part './src/barcode_generator/base/barcode_generator.dart'; -part './src/barcode_generator/base/symbology_base.dart'; -part './src/barcode_generator/one_dimensional/codabar_symbology.dart'; -part './src/barcode_generator/one_dimensional/code39_symbology.dart'; -part './src/barcode_generator/one_dimensional/code39_extended_symbology.dart'; -part './src/barcode_generator/one_dimensional/code93_symbology.dart'; -part './src/barcode_generator/one_dimensional/code128_symbology.dart'; -part './src/barcode_generator/one_dimensional/code128a_symbology.dart'; -part './src/barcode_generator/one_dimensional/code128b_symbology.dart'; -part './src/barcode_generator/one_dimensional/code128c_symbology.dart'; -part './src/barcode_generator/one_dimensional/ean8_symbology.dart'; -part './src/barcode_generator/one_dimensional/ean13_symbology.dart'; -part './src/barcode_generator/one_dimensional/upca_symbology.dart'; -part './src/barcode_generator/one_dimensional/upce_symbology.dart'; -part './src/barcode_generator/utils/helper.dart'; -part './src/barcode_generator/utils/enum.dart'; -part './src/barcode_generator/common/barcode_renderer.dart'; -part './src/barcode_generator/two_dimensional/error_correction_codewords.dart'; -part './src/barcode_generator/two_dimensional/qr_code_symbology.dart'; -part './src/barcode_generator/two_dimensional/qr_code_values.dart'; -part './src/barcode_generator/two_dimensional/datamatrix_symbology.dart'; +export './src/barcode_generator/base/barcode_generator.dart'; +export './src/barcode_generator/base/symbology_base.dart'; +export './src/barcode_generator/one_dimensional/codabar_symbology.dart'; +export './src/barcode_generator/one_dimensional/code128_symbology.dart'; +export './src/barcode_generator/one_dimensional/code128a_symbology.dart'; +export './src/barcode_generator/one_dimensional/code128b_symbology.dart'; +export './src/barcode_generator/one_dimensional/code128c_symbology.dart'; + +export './src/barcode_generator/one_dimensional/code39_extended_symbology.dart'; +export './src/barcode_generator/one_dimensional/code39_symbology.dart'; +export './src/barcode_generator/one_dimensional/code93_symbology.dart'; + +export './src/barcode_generator/one_dimensional/ean13_symbology.dart'; +export './src/barcode_generator/one_dimensional/ean8_symbology.dart'; +export './src/barcode_generator/one_dimensional/upca_symbology.dart'; +export './src/barcode_generator/one_dimensional/upce_symbology.dart'; +export './src/barcode_generator/two_dimensional/datamatrix_symbology.dart'; +export './src/barcode_generator/two_dimensional/qr_code_symbology.dart'; +export './src/barcode_generator/utils/enum.dart'; 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 78c1d0ab3..a25af0cca 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 @@ -1,4 +1,44 @@ -part of barcodes; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import '../common/barcode_renderer.dart'; +import '../one_dimensional/codabar_symbology.dart'; +import '../one_dimensional/code128_symbology.dart'; +import '../one_dimensional/code128a_symbology.dart'; +import '../one_dimensional/code128b_symbology.dart'; +import '../one_dimensional/code128c_symbology.dart'; + +import '../one_dimensional/code39_extended_symbology.dart'; +import '../one_dimensional/code39_symbology.dart'; +import '../one_dimensional/code93_symbology.dart'; + +import '../one_dimensional/ean13_symbology.dart'; +import '../one_dimensional/ean8_symbology.dart'; +import '../one_dimensional/upca_symbology.dart'; +import '../one_dimensional/upce_symbology.dart'; + +import '../renderers/one_dimensional/codabar_renderer.dart'; + +import '../renderers/one_dimensional/code128A_renderer.dart'; +import '../renderers/one_dimensional/code128B_renderer.dart'; +import '../renderers/one_dimensional/code128C_renderer.dart'; +import '../renderers/one_dimensional/code128_renderer.dart'; +import '../renderers/one_dimensional/code39_extended_renderer.dart'; +import '../renderers/one_dimensional/code39_renderer.dart'; +import '../renderers/one_dimensional/code93_renderer.dart'; +import '../renderers/one_dimensional/ean13_renderer.dart'; +import '../renderers/one_dimensional/ean8_renderer.dart'; +import '../renderers/one_dimensional/symbology_base_renderer.dart'; +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 '../two_dimensional/datamatrix_symbology.dart'; +import '../two_dimensional/qr_code_symbology.dart'; + +import '../utils/helper.dart'; +import 'symbology_base.dart'; /// Create barcode to generate and display data in a machine-readable /// industry-standard 1D and 2D barcodes. @@ -226,6 +266,8 @@ class _SfBarcodeGeneratorState extends State { /// Specifies the text size Size _textSize; + SymbologyRenderer _symbologyRenderer; + @override void didChangeDependencies() { _barcodeTheme = SfBarcodeTheme.of(context); @@ -237,22 +279,64 @@ class _SfBarcodeGeneratorState extends State { if (widget.showValue && (oldWidget.value != widget.value || oldWidget.textStyle != widget.textStyle)) { - _textSize = _measureText(widget.value.toString(), widget.textStyle); + _textSize = measureText(widget.value.toString(), widget.textStyle); + } + + if (widget.symbology != oldWidget.symbology) { + _createSymbologyRenderer(); } super.didUpdateWidget(oldWidget); } + @override + void initState() { + _createSymbologyRenderer(); + super.initState(); + } + + void _createSymbologyRenderer() { + if (widget.symbology is Codabar) { + _symbologyRenderer = CodabarRenderer(symbology: widget.symbology); + } else if (widget.symbology is Code39Extended) { + _symbologyRenderer = Code39ExtendedRenderer(symbology: widget.symbology); + } else if (widget.symbology is Code39) { + _symbologyRenderer = Code39Renderer(symbology: widget.symbology); + } else if (widget.symbology is Code93) { + _symbologyRenderer = Code93Renderer(symbology: widget.symbology); + } else if (widget.symbology is Code128) { + _symbologyRenderer = Code128Renderer(symbology: widget.symbology); + } else if (widget.symbology is Code128A) { + _symbologyRenderer = Code128ARenderer(symbology: widget.symbology); + } else if (widget.symbology is Code128B) { + _symbologyRenderer = Code128BRenderer(symbology: widget.symbology); + } else if (widget.symbology is Code128C) { + _symbologyRenderer = Code128CRenderer(symbology: widget.symbology); + } else if (widget.symbology is EAN8) { + _symbologyRenderer = EAN8Renderer(symbology: widget.symbology); + } else if (widget.symbology is EAN13) { + _symbologyRenderer = EAN13Renderer(symbology: widget.symbology); + } else if (widget.symbology is UPCA) { + _symbologyRenderer = UPCARenderer(symbology: widget.symbology); + } else if (widget.symbology is UPCE) { + _symbologyRenderer = UPCERenderer(symbology: widget.symbology); + } else if (widget.symbology is QRCode) { + _symbologyRenderer = QRCodeRenderer(symbology: widget.symbology); + } else if (widget.symbology is DataMatrix) { + _symbologyRenderer = DataMatrixRenderer(symbology: widget.symbology); + } + } + @override Widget build(BuildContext context) { if (widget.showValue && _textSize == null) { - _textSize = _measureText(widget.value.toString(), widget.textStyle); + _textSize = measureText(widget.value.toString(), widget.textStyle); } - widget.symbology._getIsValidateInput(widget.value); - widget.symbology._textSize = _textSize; + _symbologyRenderer.getIsValidateInput(widget.value); + _symbologyRenderer.textSize = _textSize; return Container( color: widget.backgroundColor ?? _barcodeTheme.backgroundColor, - child: _SfBarcodeGeneratorRenderObjectWidget( + child: SfBarcodeGeneratorRenderObjectWidget( value: widget.value, symbology: widget.symbology, foregroundColor: widget.barColor ?? _barcodeTheme.barColor, @@ -266,6 +350,7 @@ class _SfBarcodeGeneratorState extends State { fontStyle: widget.textStyle.fontStyle, fontWeight: widget.textStyle.fontWeight, textBaseline: widget.textStyle.textBaseline), + 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 820f83e66..6f6e91b74 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 @@ -1,5 +1,3 @@ -part of barcodes; - /// Define the barcode symbology that will be used to encode the input value /// to the visual barcode representation. /// @@ -47,118 +45,4 @@ abstract class Symbology { ///} /// ```dart final int module; - - /// Specifies the value with start and the stop symbol - String _valueWithStartAndStopSymbol; - - /// Specifies the text size - Size _textSize; - - /// Method to valid whether the provided input character is supported - /// by corresponding symbology - bool _getIsValidateInput(String value); - - /// 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); - - /// Renders the paint for the bar code - Paint _getBarPaint(Color foregroundColor) { - return Paint() - ..color = foregroundColor - ..strokeWidth = 1 - ..style = PaintingStyle.fill; - } - - /// Calculates the left value of the initial bar code - double _getLeftPosition( - int barWidth, int module, double width, double offsetX) { - final int calculatedWidth = barWidth * module; - // Calculates the left position of the barcode based on the provided - // module value - double diffInWidth = (width - calculatedWidth) / 2; - diffInWidth += offsetX; - return diffInWidth; - } - - /// 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]) { - final TextSpan span = TextSpan(text: value, style: textStyle); - final TextPainter textPainter = TextPainter( - text: span, textDirection: TextDirection.ltr, textAlign: textAlign); - textPainter.layout(); - double x; - double y; - if ((this is UPCA || this is EAN13 || this is UPCE) && value.length == 1) { - x = offset.dx; - y = offset.dy; - } else { - switch (textAlign) { - case TextAlign.justify: - case TextAlign.center: - { - x = (offset.dx + size.width / 2) - textPainter.width / 2; - y = offset.dy + size.height + textSpacing; - } - break; - case TextAlign.left: - case TextAlign.start: - { - x = offset.dx; - y = offset.dy + size.height + textSpacing; - } - break; - case TextAlign.right: - case TextAlign.end: - { - x = offset.dx + (size.width - textPainter.width); - y = offset.dy + size.height + textSpacing; - } - break; - } - } - - if (this is UPCE || this is UPCA || this is EAN8 || this is EAN13) { - // Checks whether the calculated x value is present inside the control - // size - if (x >= actualOffset.dx && - x + textPainter.width <= actualOffset.dx + actualSize.width) { - textPainter.paint(canvas, Offset(x, y)); - } - } else { - textPainter.paint(canvas, Offset(x, y)); - } - } - - /// Calculates whether the corresponding type has extra height barcode - bool _getHasExtraHeight(int currentItemIndex, List code) { - if (((currentItemIndex == 0 || currentItemIndex == code.length - 1) && - (this is Code39 || this is Code39Extended)) || - ((this is EAN8 || this is EAN13) && - (currentItemIndex == 0 || - currentItemIndex == 2 || - currentItemIndex == code.length - 1)) || - this is UPCA && - (currentItemIndex == 1 || - currentItemIndex == code.length - 2 || - currentItemIndex == code.length - 4) || - this is UPCE && - (currentItemIndex == 1 || - currentItemIndex == code.length - 2 || - currentItemIndex == code.length - 4)) { - return true; - } else { - return false; - } - } } 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 d4da0c1b9..d5e2e6957 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 @@ -1,9 +1,13 @@ -part of barcodes; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../base/symbology_base.dart'; +import '../renderers/one_dimensional/symbology_base_renderer.dart'; /// Represents the render object widget -class _SfBarcodeGeneratorRenderObjectWidget extends LeafRenderObjectWidget { +class SfBarcodeGeneratorRenderObjectWidget extends LeafRenderObjectWidget { /// Creates the render object widget - const _SfBarcodeGeneratorRenderObjectWidget( + const SfBarcodeGeneratorRenderObjectWidget( {Key key, this.value, this.symbology, @@ -12,6 +16,7 @@ class _SfBarcodeGeneratorRenderObjectWidget extends LeafRenderObjectWidget { this.textSpacing, this.textStyle, this.textSize, + this.symbologyRenderer, this.textAlign}) : super(key: key); @@ -40,6 +45,9 @@ class _SfBarcodeGeneratorRenderObjectWidget extends LeafRenderObjectWidget { /// Specifies the spacing between the text and the barcode. final TextAlign textAlign; + /// Specifies the corresponding renderer class + final SymbologyRenderer symbologyRenderer; + @override RenderObject createRenderObject(BuildContext context) { return _RenderBarcode( @@ -48,6 +56,7 @@ class _SfBarcodeGeneratorRenderObjectWidget extends LeafRenderObjectWidget { foregroundColor: foregroundColor, showText: showText, textSpacing: textSpacing, + symbologyRenderer: symbologyRenderer, textStyle: textStyle, textSize: textSize, textAlign: textAlign); @@ -58,6 +67,7 @@ class _SfBarcodeGeneratorRenderObjectWidget extends LeafRenderObjectWidget { renderObject ..value = value ..symbology = symbology + ..symbologyRenderer = symbologyRenderer ..foregroundColor = foregroundColor ..showText = showText ..textSpacing = textSpacing @@ -73,6 +83,7 @@ class _RenderBarcode extends RenderBox { _RenderBarcode( {@required String value, Symbology symbology, + SymbologyRenderer symbologyRenderer, Color foregroundColor, bool showText, double textSpacing, @@ -81,6 +92,7 @@ class _RenderBarcode extends RenderBox { TextAlign textAlign}) : _value = value, _symbology = symbology, + _symbologyRenderer = symbologyRenderer, _foregroundColor = foregroundColor, _showText = showText, _textSpacing = textSpacing, @@ -113,6 +125,9 @@ class _RenderBarcode extends RenderBox { /// Specifies the spacing between the text and the barcode. TextAlign _textAlign; + /// Specifies the symbology renderer corresponding to that symbology + SymbologyRenderer _symbologyRenderer; + /// Returns the value String get value => _value; @@ -137,6 +152,9 @@ class _RenderBarcode extends RenderBox { /// Returns the text align value TextAlign get textAlign => _textAlign; + /// Returns the text align value + SymbologyRenderer get symbologyRenderer => _symbologyRenderer; + /// Set the value set value(String value) { if (_value != value) { @@ -201,6 +219,14 @@ class _RenderBarcode extends RenderBox { } } + /// Sets the symbology renderer value + set symbologyRenderer(SymbologyRenderer value) { + if (_symbologyRenderer != value) { + _symbologyRenderer = value; + markNeedsPaint(); + } + } + @override void performLayout() { const double minHeight = 350; @@ -219,7 +245,7 @@ class _RenderBarcode extends RenderBox { @override void paint(PaintingContext context, Offset offset) { - symbology._renderBarcode( + symbologyRenderer.renderBarcode( context.canvas, Size( size.width, diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/codabar_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/codabar_symbology.dart index 644ffcfe5..bbf5b53e8 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/codabar_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/codabar_symbology.dart @@ -1,4 +1,4 @@ -part of barcodes; +import '../base/symbology_base.dart'; /// The [Codabar] is a discrete numeric symbology that can encode 0-9 digits, /// six symbols, and plus an additional 4 start and stop characters. @@ -7,136 +7,5 @@ class Codabar extends Symbology { /// /// The arguments [module] must be non-negative and greater than 0. /// - Codabar({int module}) : super(module: module) { - _codeBarMap = { - '0': '101010011', - '1': '101011001', - '2': '101001011', - '3': '110010101', - '4': '101101001', - '5': '110101001', - '6': '100101011', - '7': '100101101', - '8': '100110101', - '9': '110100101', - '-': '101001101', - '\$': '101100101', - ':': '1101011011', - '/': '1101101011', - '.': '1101101101', - '+': '101100110011', - 'A': '1011001001', - 'B': '1001001011', - 'C': '1010010011', - 'D': '1010011001' - }; - } - - /// Represents the supported symbol and its byte value - Map _codeBarMap; - - @override - bool _getIsValidateInput(String value) { - for (int i = 0; i < value.length; i++) { - if (!_codeBarMap.containsKey(value[i]) || - value[i] == 'A' || - value[i] == 'B' || - value[i] == 'C' || - value[i] == 'D') { - throw 'The provided input cannot be encoded : ' + value[i]; - } - } - return true; - } - - @override - void _renderBarcode( - 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 = module == null - ? offset.dx - : _getLeftPosition(barTotalLength, module, size.width, offset.dx); - final Rect barCodeRect = Rect.fromLTRB( - offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); - double ratio = 0; - if (module != null) { - ratio = module.toDouble(); - } else { - // Calculates the bar length based on number of individual bar codes - final int singleModule = (size.width ~/ barTotalLength).toInt(); - ratio = singleModule.toDouble(); - final double leftPadding = (size.width - (barTotalLength * ratio)) / 2; - left += leftPadding; - } - left = left.roundToDouble(); - for (int i = 0; i < code.length; i++) { - final String codeValue = code[i]; - const bool hasExtraHeight = false; - final double barHeight = hasExtraHeight - ? size.height + _textSize.height + textSpacing - : size.height; - final int codeLength = codeValue.length; - for (int j = 0; j < codeLength; j++) { - final bool canDraw = codeValue[j] == '1' ? true : false; - - // Draws the barcode when the corresponding bar value is one - if (canDraw && - (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { - final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + barHeight); - canvas.drawRect(individualBarRect, paint); - } - left += ratio; - } - if (i < code.length - 1) { - left += ratio; - } - } - if (showValue) { - _drawText(canvas, offset, size, value, textStyle, textSpacing, textAlign); - } - } - - /// Calculate total bar length from give input value - int _getTotalLength(List code) { - int count = 0; - for (int i = 0; i < code.length; i++) { - final int numberOfDigits = code[i].length; - count += numberOfDigits; - } - count += code.length - 1; - return count; - } - - /// Method to append the start and the stop symbol - String _getValueWithStartAndStopSymbol(String value) { - return 'A' + value + 'A'; - } - - /// Returns the encoded value of the provided input value - List _getCodeValues(String value) { - _valueWithStartAndStopSymbol = _getValueWithStartAndStopSymbol(value); - final List codeBarValues = - List(_valueWithStartAndStopSymbol.length); - for (int i = 0; i < _valueWithStartAndStopSymbol.length; i++) { - for (int j = 0; j < _codeBarMap.length; j++) { - if (_valueWithStartAndStopSymbol[i] == - _codeBarMap.entries.elementAt(j).key) { - codeBarValues[i] = _codeBarMap.entries.elementAt(j).value; - break; - } - } - } - return codeBarValues; - } + Codabar({int module}) : super(module: module); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128_symbology.dart index 36236d0fc..1a2a4f0b0 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 @@ -1,4 +1,4 @@ -part of barcodes; +import '../base/symbology_base.dart'; /// The [Code128] is a highly efficient, high-density linear barcode symbology /// that allows the encoding of alphanumeric data. It is capable of encoding @@ -21,679 +21,5 @@ class Code128 extends Symbology { Code128({int module}) : super( module: module, - ) { - _code128ACharacterSets = []; - - _code128ACharacterSets.add(' '); - _code128ACharacterSets.add('!'); - _code128ACharacterSets.add('"'); - _code128ACharacterSets.add('#'); - _code128ACharacterSets.add('\$'); - _code128ACharacterSets.add('%'); - _code128ACharacterSets.add('&'); - _code128ACharacterSets.add('\''); - _code128ACharacterSets.add('('); - _code128ACharacterSets.add(')'); - _code128ACharacterSets.add('*'); - _code128ACharacterSets.add('+'); - _code128ACharacterSets.add(','); - _code128ACharacterSets.add('-'); - _code128ACharacterSets.add('.'); - _code128ACharacterSets.add('/'); - _code128ACharacterSets.add('0'); - _code128ACharacterSets.add('1'); - _code128ACharacterSets.add('2'); - _code128ACharacterSets.add('3'); - _code128ACharacterSets.add('4'); - _code128ACharacterSets.add('5'); - _code128ACharacterSets.add('6'); - _code128ACharacterSets.add('7'); - _code128ACharacterSets.add('8'); - _code128ACharacterSets.add('9'); - _code128ACharacterSets.add(':'); - _code128ACharacterSets.add(';'); - _code128ACharacterSets.add('<'); - _code128ACharacterSets.add('='); - _code128ACharacterSets.add('>'); - _code128ACharacterSets.add('?'); - _code128ACharacterSets.add('@'); - _code128ACharacterSets.add('A'); - _code128ACharacterSets.add('B'); - _code128ACharacterSets.add('C'); - _code128ACharacterSets.add('D'); - _code128ACharacterSets.add('E'); - _code128ACharacterSets.add('F'); - _code128ACharacterSets.add('G'); - _code128ACharacterSets.add('H'); - _code128ACharacterSets.add('I'); - _code128ACharacterSets.add('J'); - _code128ACharacterSets.add('K'); - _code128ACharacterSets.add('L'); - _code128ACharacterSets.add('M'); - _code128ACharacterSets.add('N'); - _code128ACharacterSets.add('O'); - _code128ACharacterSets.add('P'); - _code128ACharacterSets.add('Q'); - _code128ACharacterSets.add('R'); - _code128ACharacterSets.add('S'); - _code128ACharacterSets.add('T'); - _code128ACharacterSets.add('U'); - _code128ACharacterSets.add('V'); - _code128ACharacterSets.add('W'); - _code128ACharacterSets.add('X'); - _code128ACharacterSets.add('Y'); - _code128ACharacterSets.add('Z'); - _code128ACharacterSets.add('['); - _code128ACharacterSets.add('\\'); - _code128ACharacterSets.add(']'); - _code128ACharacterSets.add('^'); - _code128ACharacterSets.add('_'); - _code128ACharacterSets.add('\0'); - _code128ACharacterSets.add('\u0001'); - _code128ACharacterSets.add('\u0002'); - _code128ACharacterSets.add('\u0003'); - _code128ACharacterSets.add('\u0004'); - _code128ACharacterSets.add('\u0005'); - _code128ACharacterSets.add('\u0006'); - _code128ACharacterSets.add('\a'); - _code128ACharacterSets.add('\b'); - _code128ACharacterSets.add('\t'); - _code128ACharacterSets.add('\n'); - _code128ACharacterSets.add('\v'); - _code128ACharacterSets.add('\f'); - _code128ACharacterSets.add('\r'); - _code128ACharacterSets.add('\u000e'); - _code128ACharacterSets.add('\u000f'); - _code128ACharacterSets.add('\u0010'); - _code128ACharacterSets.add('\u0011'); - _code128ACharacterSets.add('\u0012'); - _code128ACharacterSets.add('\u0013'); - _code128ACharacterSets.add('\u0014'); - _code128ACharacterSets.add('\u0015'); - _code128ACharacterSets.add('\u0016'); - _code128ACharacterSets.add('\u0017'); - _code128ACharacterSets.add('\u0018'); - _code128ACharacterSets.add('\u0019'); - _code128ACharacterSets.add('\u001a'); - _code128ACharacterSets.add('\u001b'); - _code128ACharacterSets.add('\u001c'); - _code128ACharacterSets.add('\u001d'); - _code128ACharacterSets.add('\u001e'); - _code128ACharacterSets.add('\u001f'); - _code128ACharacterSets.add('ù'); - _code128ACharacterSets.add('ø'); - _code128ACharacterSets.add('û'); - _code128ACharacterSets.add('ö'); - _code128ACharacterSets.add('õ'); - _code128ACharacterSets.add('ú'); - _code128ACharacterSets.add('÷'); - _code128ACharacterSets.add('ü'); - _code128ACharacterSets.add('ý'); - _code128ACharacterSets.add('þ'); - _code128ACharacterSets.add('ÿ'); - - _code128BCharacterSets = []; - _code128BCharacterSets.add(' '); - _code128BCharacterSets.add('!'); - _code128BCharacterSets.add('"'); - _code128BCharacterSets.add('#'); - _code128BCharacterSets.add('\$'); - _code128BCharacterSets.add('%'); - _code128BCharacterSets.add('&'); - _code128BCharacterSets.add('\''); - _code128BCharacterSets.add('('); - _code128BCharacterSets.add(')'); - _code128BCharacterSets.add('*'); - _code128BCharacterSets.add('+'); - _code128BCharacterSets.add(','); - _code128BCharacterSets.add('-'); - _code128BCharacterSets.add('.'); - _code128BCharacterSets.add('/'); - _code128BCharacterSets.add('0'); - _code128BCharacterSets.add('1'); - _code128BCharacterSets.add('2'); - _code128BCharacterSets.add('3'); - _code128BCharacterSets.add('4'); - _code128BCharacterSets.add('5'); - _code128BCharacterSets.add('6'); - _code128BCharacterSets.add('7'); - _code128BCharacterSets.add('8'); - _code128BCharacterSets.add('9'); - _code128BCharacterSets.add(':'); - _code128BCharacterSets.add(';'); - _code128BCharacterSets.add('<'); - _code128BCharacterSets.add('='); - _code128BCharacterSets.add('>'); - _code128BCharacterSets.add('?'); - _code128BCharacterSets.add('@'); - _code128BCharacterSets.add('A'); - _code128BCharacterSets.add('B'); - _code128BCharacterSets.add('C'); - _code128BCharacterSets.add('D'); - _code128BCharacterSets.add('E'); - _code128BCharacterSets.add('F'); - _code128BCharacterSets.add('G'); - _code128BCharacterSets.add('H'); - _code128BCharacterSets.add('I'); - _code128BCharacterSets.add('J'); - _code128BCharacterSets.add('K'); - _code128BCharacterSets.add('L'); - _code128BCharacterSets.add('M'); - _code128BCharacterSets.add('N'); - _code128BCharacterSets.add('O'); - _code128BCharacterSets.add('P'); - _code128BCharacterSets.add('Q'); - _code128BCharacterSets.add('R'); - _code128BCharacterSets.add('S'); - _code128BCharacterSets.add('T'); - _code128BCharacterSets.add('U'); - _code128BCharacterSets.add('V'); - _code128BCharacterSets.add('W'); - _code128BCharacterSets.add('X'); - _code128BCharacterSets.add('Y'); - _code128BCharacterSets.add('Z'); - _code128BCharacterSets.add('['); - _code128BCharacterSets.add('\\'); - _code128BCharacterSets.add(']'); - _code128BCharacterSets.add('^'); - _code128BCharacterSets.add('_'); - _code128BCharacterSets.add('`'); - _code128BCharacterSets.add('a'); - _code128BCharacterSets.add('b'); - _code128BCharacterSets.add('c'); - _code128BCharacterSets.add('d'); - _code128BCharacterSets.add('e'); - _code128BCharacterSets.add('f'); - _code128BCharacterSets.add('g'); - _code128BCharacterSets.add('h'); - _code128BCharacterSets.add('i'); - _code128BCharacterSets.add('j'); - _code128BCharacterSets.add('k'); - _code128BCharacterSets.add('l'); - _code128BCharacterSets.add('m'); - _code128BCharacterSets.add('n'); - _code128BCharacterSets.add('o'); - _code128BCharacterSets.add('p'); - _code128BCharacterSets.add('q'); - _code128BCharacterSets.add('r'); - _code128BCharacterSets.add('s'); - _code128BCharacterSets.add('t'); - _code128BCharacterSets.add('u'); - _code128BCharacterSets.add('v'); - _code128BCharacterSets.add('w'); - _code128BCharacterSets.add('x'); - _code128BCharacterSets.add('y'); - _code128BCharacterSets.add('z'); - _code128BCharacterSets.add('{'); - _code128BCharacterSets.add('|'); - _code128BCharacterSets.add('}'); - _code128BCharacterSets.add('~'); - _code128BCharacterSets.add('\u007f'); - _code128BCharacterSets.add('ù'); - _code128BCharacterSets.add('ø'); - _code128BCharacterSets.add('û'); - _code128BCharacterSets.add('ö'); - _code128BCharacterSets.add('ú'); - _code128BCharacterSets.add('ô'); - _code128BCharacterSets.add('÷'); - _code128BCharacterSets.add('ü'); - _code128BCharacterSets.add('ý'); - _code128BCharacterSets.add('þ'); - _code128BCharacterSets.add('ÿ'); - - _code128CCharacterSets = []; - _code128CCharacterSets.add('0'); - _code128CCharacterSets.add('1'); - _code128CCharacterSets.add('2'); - _code128CCharacterSets.add('3'); - _code128CCharacterSets.add('4'); - _code128CCharacterSets.add('5'); - _code128CCharacterSets.add('6'); - _code128CCharacterSets.add('7'); - _code128CCharacterSets.add('8'); - _code128CCharacterSets.add('9'); - _code128CCharacterSets.add('õ'); - _code128CCharacterSets.add('ô'); - _code128CCharacterSets.add('÷'); - _code128CCharacterSets.add('ü'); - _code128CCharacterSets.add('ý'); - _code128CCharacterSets.add('þ'); - _code128CCharacterSets.add('ÿ'); - } - - /// Represents the start symbol of code128A - static const int _codeAStartSymbol = 103; - - /// Represents the start symbol for Code128B - static const int _codeBStartSymbol = 104; - - /// Represents the start symbol for Code128C - static const int _codeCStartSymbol = 105; - - /// Specifies the value for code128A - static const int _codeA = 101; - - /// Specifies the value for code128B - static const int _codeB = 100; - - /// Specifies the value for code128C - static const int _codeC = 99; - - /// Specifies the stop symbol - static const int _codeStopSymbol = 106; - - /// Represents the index value of FNC1 special character - static const int _codeFNC1 = 102; - - /// Represents the index value of FNC2 special character - static const int _codeFNC2 = 97; - - /// Represents the index value of FNC3 special character - static const int _codeFNC3 = 96; - - /// Represents the index value of FNC4A special character - static const int _codeFNC4A = 101; - - /// Represents the index value of FNC4B special character - static const int _codeFNC4B = 100; - - /// Represents the FNC1 special character - static const String _fnc1 = '\u00f1'; - - /// Represents the FNC2 special character - static const String _fnc2 = '\u00f2'; - - /// Represents the FNC3 special character - static const String _fnc3 = '\u00f3'; - - /// Represents the FNC4 special character - static const String _fnc4 = '\u00f4'; - - /// Represents the supported symbol character of code128A - List _code128ACharacterSets; - - /// Represents the supported symbol character of code128B - List _code128BCharacterSets; - - /// Represents the supported symbol character of code128C - List _code128CCharacterSets; - - /// Returns the byte value of supported symbol - /// - /// This is quite a large method. This method could not be - /// refactored to a smaller methods, since the code value corresponds to this - /// symbology is added into the collection - List> _getCodeValue() { - return >[ - [2, 1, 2, 2, 2, 2], - [2, 2, 2, 1, 2, 2], - [2, 2, 2, 2, 2, 1], - [1, 2, 1, 2, 2, 3], - [1, 2, 1, 3, 2, 2], - [1, 3, 1, 2, 2, 2], - [1, 2, 2, 2, 1, 3], - [1, 2, 2, 3, 1, 2], - [1, 3, 2, 2, 1, 2], - [2, 2, 1, 2, 1, 3], - [2, 2, 1, 3, 1, 2], - [2, 3, 1, 2, 1, 2], - [1, 1, 2, 2, 3, 2], - [1, 2, 2, 1, 3, 2], - [1, 2, 2, 2, 3, 1], - [1, 1, 3, 2, 2, 2], - [1, 2, 3, 1, 2, 2], - [1, 2, 3, 2, 2, 1], - [2, 2, 3, 2, 1, 1], - [2, 2, 1, 1, 3, 2], - [2, 2, 1, 2, 3, 1], - [2, 1, 3, 2, 1, 2], - [2, 2, 3, 1, 1, 2], - [3, 1, 2, 1, 3, 1], - [3, 1, 1, 2, 2, 2], - [3, 2, 1, 1, 2, 2], - [3, 2, 1, 2, 2, 1], - [3, 1, 2, 2, 1, 2], - [3, 2, 2, 1, 1, 2], - [3, 2, 2, 2, 1, 1], - [2, 1, 2, 1, 2, 3], - [2, 1, 2, 3, 2, 1], - [2, 3, 2, 1, 2, 1], - [1, 1, 1, 3, 2, 3], - [1, 3, 1, 1, 2, 3], - [1, 3, 1, 3, 2, 1], - [1, 1, 2, 3, 1, 3], - [1, 3, 2, 1, 1, 3], - [1, 3, 2, 3, 1, 1], - [2, 1, 1, 3, 1, 3], - [2, 3, 1, 1, 1, 3], - [2, 3, 1, 3, 1, 1], - [1, 1, 2, 1, 3, 3], - [1, 1, 2, 3, 3, 1], - [1, 3, 2, 1, 3, 1], - [1, 1, 3, 1, 2, 3], - [1, 1, 3, 3, 2, 1], - [1, 3, 3, 1, 2, 1], - [3, 1, 3, 1, 2, 1], - [2, 1, 1, 3, 3, 1], - [2, 3, 1, 1, 3, 1], - [2, 1, 3, 1, 1, 3], - [2, 1, 3, 3, 1, 1], - [2, 1, 3, 1, 3, 1], - [3, 1, 1, 1, 2, 3], - [3, 1, 1, 3, 2, 1], - [3, 3, 1, 1, 2, 1], - [3, 1, 2, 1, 1, 3], - [3, 1, 2, 3, 1, 1], - [3, 3, 2, 1, 1, 1], - [3, 1, 4, 1, 1, 1], - [2, 2, 1, 4, 1, 1], - [4, 3, 1, 1, 1, 1], - [1, 1, 1, 2, 2, 4], - [1, 1, 1, 4, 2, 2], - [1, 2, 1, 1, 2, 4], - [1, 2, 1, 4, 2, 1], - [1, 4, 1, 1, 2, 2], - [1, 4, 1, 2, 2, 1], - [1, 1, 2, 2, 1, 4], - [1, 1, 2, 4, 1, 2], - [1, 2, 2, 1, 1, 4], - [1, 2, 2, 4, 1, 1], - [1, 4, 2, 1, 1, 2], - [1, 4, 2, 2, 1, 1], - [2, 4, 1, 2, 1, 1], - [2, 2, 1, 1, 1, 4], - [4, 1, 3, 1, 1, 1], - [2, 4, 1, 1, 1, 2], - [1, 3, 4, 1, 1, 1], - [1, 1, 1, 2, 4, 2], - [1, 2, 1, 1, 4, 2], - [1, 2, 1, 2, 4, 1], - [1, 1, 4, 2, 1, 2], - [1, 2, 4, 1, 1, 2], - [1, 2, 4, 2, 1, 1], - [4, 1, 1, 2, 1, 2], - [4, 2, 1, 1, 1, 2], - [4, 2, 1, 2, 1, 1], - [2, 1, 2, 1, 4, 1], - [2, 1, 4, 1, 2, 1], - [4, 1, 2, 1, 2, 1], - [1, 1, 1, 1, 4, 3], - [1, 1, 1, 3, 4, 1], - [1, 3, 1, 1, 4, 1], - [1, 1, 4, 1, 1, 3], - [1, 1, 4, 3, 1, 1], - [4, 1, 1, 1, 1, 3], - [4, 1, 1, 3, 1, 1], - [1, 1, 3, 1, 4, 1], - [1, 1, 4, 1, 3, 1], - [3, 1, 1, 1, 4, 1], - [4, 1, 1, 1, 3, 1], - [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] - ]; - } - - @override - bool _getIsValidateInput(String value) { - for (int i = 0; i < value.length; i++) { - final int currentCharacter = value[i].codeUnitAt(0); - if (currentCharacter == _fnc1.codeUnitAt(0) || - currentCharacter == _fnc2.codeUnitAt(0) || - currentCharacter == _fnc3.codeUnitAt(0) || - currentCharacter == _fnc4.codeUnitAt(0)) { - return true; - } else if (currentCharacter < 127) { - return true; - } else { - throw 'The provided input cannot be encoded : ' + value[i]; - } - } - return false; - } - - /// Returns the encoded value - List> _getEncodedValue(String value) { - final List> encodedValue = >[]; - final List> bytes = _getCodeValue(); - int checkDigit = 0; - int weightValue = 1; - int codeTypeValue = 0; - int currentPosition = 0; - while (currentPosition < value.length) { - final int currentCodeType = - _getValidatedCode(currentPosition, codeTypeValue, value); - int currentIndex; - if (currentCodeType == codeTypeValue) { - final int currentValue = value[currentPosition].codeUnitAt(0); - if (currentValue == _fnc1.codeUnitAt(0)) { - currentIndex = _codeFNC1; - } else if (currentValue == _fnc2.codeUnitAt(0)) { - currentIndex = _codeFNC2; - } else if (currentValue == _fnc3.codeUnitAt(0)) { - currentIndex = _codeFNC3; - } else if (currentValue == _fnc4.codeUnitAt(0)) { - if (currentCodeType == _codeA) { - currentIndex = _codeFNC4A; - } else { - currentIndex = _codeFNC4B; - } - } else { - // Calculates the current index value based on code128 type - if (currentCodeType == _codeA) { - currentIndex = - value[currentPosition].codeUnitAt(0) - ' '.codeUnitAt(0); - if (currentIndex < 0) { - currentIndex += '`'.codeUnitAt(0); - } - } else if (currentCodeType == _codeB) { - currentIndex = - value[currentPosition].codeUnitAt(0) - ' '.codeUnitAt(0); - } else { - currentIndex = int.parse( - value.substring(currentPosition, currentPosition + 2)); - currentPosition++; - } - } - currentPosition++; - } else { - currentIndex = _getCurrentIndex(codeTypeValue, currentCodeType); - codeTypeValue = currentCodeType; - } - encodedValue.add(bytes[currentIndex]); - checkDigit += currentIndex * weightValue; - if (currentPosition != 0) { - weightValue++; - } - } - checkDigit %= 103; - encodedValue.add(bytes[checkDigit]); - encodedValue.add(bytes[_codeStopSymbol]); - return encodedValue; - } - - /// Method to get the current index value - int _getCurrentIndex(int codeTypeValue, int currentCodeType) { - int currentIndex; - if (codeTypeValue == 0) { - if (currentCodeType == _codeA) { - currentIndex = _codeAStartSymbol; - } else if (currentCodeType == _codeB) { - currentIndex = _codeBStartSymbol; - } else { - currentIndex = _codeCStartSymbol; - } - } else { - currentIndex = currentCodeType; - } - - return currentIndex; - } - - /// 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); - if (currentCodeType != null) { - return currentCodeType; - } - if (previousCodeSet == _codeB) { - if (codeType == CodeType.fnc1) { - return _codeB; - } - codeType = _getCodeType(start + 2, value); - if (codeType == CodeType.uncodable || codeType == CodeType.singleDigit) { - return _codeB; - } - if (codeType == CodeType.fnc1) { - codeType = _getCodeType(start + 3, value); - if (codeType == CodeType.doubleDigit) { - return this is Code128C ? _codeC : _codeB; - } else { - return _codeB; - } - } - int currentIndex = start + 4; - while (_getCodeType(currentIndex, value) == CodeType.doubleDigit) { - currentIndex += 2; - } - if (codeType == CodeType.singleDigit) { - return _codeB; - } - return this is Code128B ? _codeB : _codeC; - } - - if (codeType == CodeType.fnc1) { - codeType = _getCodeType(start + 1, value); - } - - if (codeType == CodeType.doubleDigit) { - return this is Code128B ? _codeB : _codeC; - } - return _codeB; - } - - /// Method to get the validated types - int _getValidatedCodeTypes( - int start, int previousCodeSet, String value, CodeType codeType) { - if (codeType == CodeType.singleDigit) { - if (previousCodeSet == _codeA) { - return _codeA; - } - return _codeB; - } - - if (codeType == CodeType.uncodable) { - if (start < value.length) { - final int startIndex = value[start].codeUnitAt(0); - if (startIndex < ' '.codeUnitAt(0) || - (previousCodeSet == _codeA && - (startIndex < '`'.codeUnitAt(0) || - (startIndex >= _fnc1.codeUnitAt(0) && - startIndex <= _fnc4.codeUnitAt(0))))) { - return _codeA; - } - } - return _codeB; - } - if (previousCodeSet == _codeA && codeType == CodeType.fnc1) { - return _codeA; - } - if (previousCodeSet == _codeC) { - return _codeC; - } - - return null; - } - - /// Returns the code type based on the input - CodeType _getCodeType(int startIndex, String value) { - final int length = value.length; - if (startIndex >= length) { - return CodeType.uncodable; - } - if (String.fromCharCode(value[startIndex].codeUnitAt(0)) == - String.fromCharCode(_fnc1.codeUnitAt(0))) { - return CodeType.fnc1; - } - if (value[startIndex].codeUnitAt(0) < '0'.codeUnitAt(0) || - value[startIndex].codeUnitAt(0) > '9'.codeUnitAt(0)) { - return CodeType.uncodable; - } - - if (startIndex + 1 >= length) { - return CodeType.singleDigit; - } - - if (value[startIndex + 1].codeUnitAt(0) < '0'.codeUnitAt(0) || - value[startIndex + 1].codeUnitAt(0) > '9'.codeUnitAt(0)) { - return CodeType.singleDigit; - } - - return CodeType.doubleDigit; - } - - @override - void _renderBarcode( - 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 = module == null - ? offset.dx - : _getLeftPosition(totalBarLength, module, size.width, offset.dx); - double ratio = 0; - if (module != null) { - ratio = module.toDouble(); - } else { - // Calculates the bar length based on number of individual bar codes - final int singleModule = (size.width ~/ totalBarLength).toInt(); - ratio = singleModule.toDouble(); - final double leftPadding = (size.width - (totalBarLength * ratio)) / 2; - left += leftPadding; - } - left = left.roundToDouble(); - for (int i = 0; i < encodedValue.length; i++) { - bool canDraw = true; - final List currentIndex = encodedValue[i]; - for (int j = 0; j < currentIndex.length; j++) { - final int currentValue = currentIndex[j]; - // Draws the bar code based on the current value - for (int k = 0; k < currentValue; k++) { - if (canDraw) { - final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + size.height); - canvas.drawRect(individualBarRect, paint); - } - left += ratio; - } - canDraw = !canDraw; - } - } - if (showValue) { - _drawText(canvas, offset, size, value, textStyle, textSpacing, textAlign); - } - } - - /// Calculate total bar length from give input value - int _getTotalBarLength(List> encodedValue) { - int length = 0; - for (int i = 0; i < encodedValue.length; i++) { - final List currentValue = encodedValue[i]; - for (int j = 0; j < currentValue.length; j++) { - length += currentValue[j]; - } - } - return length; - } + ); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128a_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128a_symbology.dart index c4d8549c2..0cf5a17d4 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128a_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128a_symbology.dart @@ -1,4 +1,4 @@ -part of barcodes; +import '../one_dimensional/code128_symbology.dart'; /// The [Code128A] (or chars set A) barcode includes all the standard upper /// cases, alphanumeric keyboard characters and punctuation characters together @@ -10,14 +10,4 @@ class Code128A extends Code128 { /// The arguments [module] must be non-negative and greater than 0. /// Code128A({int module}) : super(module: module); - - @override - 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]; - } - } - return true; - } } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128b_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128b_symbology.dart index c88e0eed8..e6253ac0c 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128b_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128b_symbology.dart @@ -1,4 +1,4 @@ -part of barcodes; +import '../one_dimensional/code128_symbology.dart'; /// The [Code128B] (or chars set B) barcode includes all the standard upper /// case, alphanumeric keyboard characters and punctuation characters @@ -10,14 +10,4 @@ class Code128B extends Code128 { /// The arguments [module] must be non-negative and greater than 0. /// Code128B({int module}) : super(module: module); - - @override - 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]; - } - } - return true; - } } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128c_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128c_symbology.dart index e2b9c3f46..26e0064d3 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128c_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128c_symbology.dart @@ -1,4 +1,4 @@ -part of barcodes; +import '../one_dimensional/code128_symbology.dart'; /// The [Code128C] (or chars set C) barcode includes a set of 100 digit pairs /// from 00 to 99 inclusive, as well as three special characters. @@ -10,14 +10,4 @@ class Code128C extends Code128 { /// The arguments [module] must be non-negative and greater than 0. /// Code128C({int module}) : super(module: module); - - @override - 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]; - } - } - return true; - } } 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 9c54d576c..e3c9d4b0c 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 @@ -1,4 +1,4 @@ -part of barcodes; +import 'code39_symbology.dart'; /// The [Code39Extended] is an extended version of [Code39]. /// Lower characters and special characters are additionally supported. @@ -18,163 +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) { - _code39ExtendedMap = { - '0': '%U', - '1': '\$A', - '2': '\$B', - '3': '\$C', - '4': '\$D', - '5': '\$E', - '6': '\$F', - '7': '\$G', - '8': '\$H', - '9': '\$I', - '10': '\$J', - '11': '\$K', - '12': '\$L', - '13': '\$M', - '14': '\$N', - '15': '\$O', - '16': '\$P', - '17': '\$Q', - '18': '\$R', - '19': '\$S', - '20': '\$T', - '21': '\$U', - '22': '\$V', - '23': '\$W', - '24': '\$X', - '25': '\$Y', - '26': '\$Z', - '27': '%A', - '28': '%B', - '29': '%C', - '30': '%D', - '31': '%E', - '32': ' ', - '33': '/A', - '34': '/B', - '35': '/C', - '36': '/D', - '37': '/E', - '38': '/F', - '39': '/G', - '40': '/H', - '41': '/I', - '42': '/J', - '43': '/K', - '44': '/L', - '45': '-', - '46': '.', - '47': '/O', - '48': '0', - '49': '1', - '50': '2', - '51': '3', - '52': '4', - '53': '5', - '54': '6', - '55': '7', - '56': '8', - '57': '9', - '58': '/Z', - '59': '%F', - '60': '%G', - '61': '%H', - '62': '%I', - '63': '%J', - '64': '%V', - '65': 'A', - '66': 'B', - '67': 'C', - '68': 'D', - '69': 'E', - '70': 'F', - '71': 'G', - '72': 'H', - '73': 'I', - '74': 'J', - '75': 'K', - '76': 'L', - '77': 'M', - '78': 'N', - '79': 'O', - '80': 'P', - '81': 'Q', - '82': 'R', - '83': 'S', - '84': 'T', - '85': 'U', - '86': 'V', - '87': 'W', - '88': 'X', - '89': 'Y', - '90': 'Z', - '91': '%K', - '92': '%L', - '93': '%M', - '94': '%N', - '95': '%O', - '96': '%W', - '97': '+A', - '98': '+B', - '99': '+C', - '100': '+D', - '101': '+E', - '102': '+F', - '103': '+G', - '104': '+H', - '105': '+I', - '106': '+J', - '107': '+K', - '108': '+L', - '109': '+M', - '110': '+N', - '111': '+O', - '112': '+P', - '113': '+Q', - '114': '+R', - '115': '+S', - '116': '+T', - '117': '+U', - '118': '+V', - '119': '+W', - '120': '+X', - '121': '+Y', - '122': '+Z', - '123': '%P', - '124': '%Q', - '125': ' %R', - '126': '%S', - '127': '%T', - }; - } - - /// Map to stores the input character and its index - Map _code39ExtendedMap; - - /// To validate the provided input value - @override - 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]; - } - } - return true; - } - - /// Returns the encoded byte value for the provided value - @override - List _getCodeValues(String value) { - String encodedString = ''; - for (int i = 0; i < value.length; i++) { - final int asciiValue = value[i].codeUnitAt(0); - final String actualValue = - _code39ExtendedMap.entries.elementAt(asciiValue).value; - encodedString += actualValue; - } - return _getEncodedValue(encodedString); - } + : super(module: module, enableCheckSum: enableCheckSum ?? true); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code39_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code39_symbology.dart index 6fb07c372..cfd1d278e 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code39_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code39_symbology.dart @@ -1,4 +1,4 @@ -part of barcodes; +import '../base/symbology_base.dart'; /// The [Code39] is a discrete, variable-length symbology that encodes /// alphanumeric characters into a series of bars. @@ -23,56 +23,7 @@ class Code39 extends Symbology { /// a modulo 43 checksum can be added, /// if the [enableCheckSum] is true. /// - Code39({int module, this.enableCheckSum = true}) : super(module: module) { - _code39Symbology = [ - '111221211', - '211211112', - '112211112', - '212211111', - '111221112', - '211221111', - '112221111', - '111211212', - '211211211', - '112211211', - '211112112', - '112112112', - '212112111', - '111122112', - '211122111', - '112122111', - '111112212', - '211112211', - '112112211', - '111122211', - '211111122', - '112111122', - '212111121', - '111121122', - '211121121', - '112121121', - '111111222', - '211111221', - '112111221', - '111121221', - '221111112', - '122111112', - '222111111', - '121121112', - '221121111', - '122121111', - '121111212', - '221111211', - '122111211', - '121121211', - '121212111', - '121211121', - '121112121', - '111212121' - ]; - - _character = _getCode39Character(); - } + Code39({int module, this.enableCheckSum = true}) : super(module: module); /// Whether to add a checksum on the far right side of the barcode. /// @@ -91,141 +42,4 @@ class Code39 extends Symbology { ///} /// ```dart final bool enableCheckSum; - - /// Represents the code39 symbology - List _code39Symbology; - - /// Represnts the encoded input character - String _character; - - @override - 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]; - } - } - return true; - } - - /// Calculates the check sum value based on the input - String _getCheckSum(String value, String codeBarCharacters) { - int checkSum = 0; - for (int i = 0; i < value.length; i++) { - final int codeNumber = codeBarCharacters.indexOf(value[i]); - checkSum += codeNumber; - } - checkSum = checkSum % 43; - return checkSum.toString(); - } - - /// Returns the provided value with start and stop symbol - String _getValueWithStartAndStopCharacters(String value) { - return '*' + value + '*'; - } - - /// Returns the pattern collection based on the provided input - List _getPatternCollection( - String providedValue, String code39Characters) { - final List code39Values = []; - for (int i = 0; i < providedValue.length; i++) { - final int currentIndex = code39Characters.indexOf(providedValue[i]); - code39Values.add(_code39Symbology[currentIndex]); - } - return code39Values; - } - - @override - void _renderBarcode( - 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 = module == null - ? offset.dx - : _getLeftPosition(barTotalLength, module, size.width, offset.dx); - final Rect barCodeRect = Rect.fromLTRB( - offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); - double ratio = 0; - if (module != null) { - ratio = module.toDouble(); - } else { - // Calculates the bar length based on number of individual bar codes - final int singleModule = (size.width ~/ barTotalLength).toInt(); - ratio = singleModule.toDouble(); - final double leftPadding = (size.width - (barTotalLength * ratio)) / 2; - left += leftPadding; - } - left = left.roundToDouble(); - for (int i = 0; i < code.length; i++) { - final String codeValue = code[i]; - const bool hasExtraHeight = false; - final double barHeight = hasExtraHeight - ? size.height + _textSize.height + textSpacing - : size.height; - final int codeLength = codeValue.length; - for (int j = 0; j < codeLength; j++) { - // The current bar is drawn, if its value is divisible by 2 - final bool canDraw = j % 2 == 0 ? true : false; - final int currentValue = int.parse(codeValue[j]); - if (canDraw && - (left >= barCodeRect.left && - left + (currentValue * ratio) < barCodeRect.right)) { - final Rect individualBarRect = Rect.fromLTRB(left, offset.dy, - left + (currentValue * ratio), offset.dy + barHeight); - canvas.drawRect(individualBarRect, paint); - } - left += currentValue * ratio; - } - if (i < code.length - 1) { - left += ratio; - } - } - if (showValue) { - _drawText(canvas, offset, size, value, textStyle, textSpacing, textAlign); - } - } - - /// Returns the encoded value - List _getCodeValues(String value) { - return _getEncodedValue(value); - } - - /// Calculate total bar length from give input value - int _getTotalLength(List code) { - int count = 0; - for (int i = 0; i < code.length; i++) { - final String currentItem = code[i]; - for (int j = 0; j < currentItem.length; j++) { - count += int.parse(currentItem[j]); - } - } - count += code.length - 1; - return count; - } - - /// Represents the encoded value for provided input - List _getEncodedValue(String providedValue) { - if (enableCheckSum) { - final String checkSum = _getCheckSum(providedValue, _character); - providedValue += checkSum; - } - providedValue = _getValueWithStartAndStopCharacters(providedValue); - return _getPatternCollection(providedValue, _character); - } - - /// Represents the code bar value - String _getCode39Character() { - const String code39Character = - '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *\$/+%'; - return code39Character; - } } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code93_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code93_symbology.dart index 265faa342..cbce95508 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code93_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code93_symbology.dart @@ -1,4 +1,4 @@ -part of barcodes; +import '../base/symbology_base.dart'; /// The [Code93] was designed to complement and improve [Code39]. It is used to /// represent the full ASCII character set by using combinations of two @@ -14,248 +14,5 @@ class Code93 extends Symbology { /// high level of accuracy. /// The checksum character is the modulo 47 remainder of the sum of the /// weighted value of the data characters. - Code93({int module}) : super(module: module) { - _character = _getCode93Character(); - } - - /// Represents the input value - String _character; - - @override - 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]; - } - } - return true; - } - - /// Returns the weight for the supported input character - Map _getCharacterWeight() { - return { - '0': '0', - '1': '1', - '2': '2', - '3': '3', - '4': '4', - '5': '5', - '6': '6', - '7': '7', - '8': '8', - '9': '9', - 'A': '10', - 'B': '11', - 'C': '12', - 'D': '13', - 'E': '14', - 'F': '15', - 'G': '16', - 'H': '17', - 'I': '18', - 'J': '19', - 'K': '20', - 'L': '21', - 'M': '22', - 'N': '23', - 'O': '24', - 'P': '25', - 'Q': '26', - 'R': '27', - 'S': '28', - 'T': '29', - 'U': '30', - 'V': '31', - 'W': '32', - 'X': '33', - 'Y': '34', - 'Z': '35', - '-': '36', - '.': '37', - ' ': '38', - '\$': '39', - '/': '40', - '+': '41', - '%': '42', - '(\$)': '43', - '(/)': '44', - '(+)': '45', - '(%)': '46', - }; - } - - /// Returns the byte value of the supported input symbol - Map _getCodeValue() { - return { - '0': '100010100', - '1': '101001000', - '2': '101000100', - '3': '101000010', - '4': '100101000', - '5': '100100100', - '6': '100100010', - '7': '101010000', - '8': '100010010', - '9': '100001010', - 'A': '110101000', - 'B': '110100100', - 'C': '110100010', - 'D': '110010100', - 'E': '110010010', - 'F': '110001010', - 'G': '101101000', - 'H': '101100100', - 'I': '101100010', - 'J': '100110100', - 'K': '100011010', - 'L': '101011000', - 'M': '101001100', - 'N': '101000110', - 'O': '100101100', - 'P': '100010110', - 'Q': '110110100', - 'R': '110110010', - 'S': '110101100', - 'T': '110100110', - 'U': '110010110', - 'V': '110011010', - 'W': '101101100', - 'X': '101100110', - 'Y': '100110110', - 'Z': '100111010', - '-': '100101110', - '.': '111010100', - ' ': '111010010', - '\$': '111001010', - '/': '101101110', - '+': '101110110', - '%': '110101110', - '(\$)': '100100110', - '(/)': '111010110', - '(+)': '100110010', - '(%)': '111011010', - }; - } - - @override - void _renderBarcode( - 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 = module == null - ? offset.dx - : _getLeftPosition(barTotalLength, module, size.width, offset.dx); - final Rect barCodeRect = Rect.fromLTRB( - offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); - double ratio = 0; - if (module != null) { - ratio = module.toDouble(); - } else { - //Calculates the bar length based on number of individual bar codes - final int singleModule = (size.width ~/ barTotalLength).toInt(); - ratio = singleModule.toDouble(); - final double leftPadding = (size.width - (barTotalLength * ratio)) / 2; - left += leftPadding; - } - left = left.roundToDouble(); - for (int i = 0; i < code.length; i++) { - final String codeValue = code[i]; - const bool hasExtraHeight = false; - final double barHeight = hasExtraHeight - ? size.height + _textSize.height + textSpacing - : size.height; - final int codeLength = codeValue.length; - for (int j = 0; j < codeLength; j++) { - //Draws the barcode when the corresponding bar value is one - final bool canDraw = codeValue[j] == '1' ? true : false; - if (canDraw && - (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { - final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + barHeight); - canvas.drawRect(individualBarRect, paint); - } - left += ratio; - } - } - if (showValue) { - _drawText(canvas, offset, size, value, textStyle, textSpacing, textAlign); - } - } - - /// Represents the pattern collection based on the provided input - 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]); - encodingValue.add(codes.entries.elementAt(index).value); - } - return encodingValue; - } - - /// Calculate total bar length from give input value - int _getTotalLength(List code) { - int count = 0; - for (int i = 0; i < code.length; i++) { - final int numberOfDigits = code[i].length; - count += numberOfDigits; - } - return count; - } - - /// Calculates the check sum value - String _getCheckSum(String givenCharacter) { - final String value = givenCharacter; - int weightSum = 0; - int j = 0; - int moduleValue; - String appendSymbol; - final Map codes = _getCharacterWeight(); - final List codeKey = codes.keys.toList(); - for (int i = value.length; i > 0; i--) { - final int index = codeKey.indexOf(value[j]); - final int characterValue = - int.parse(codes.entries.elementAt(index).value) * i; - weightSum += characterValue; - j++; - } - moduleValue = weightSum % 47; - final List objectValue = codes.keys.toList(); - appendSymbol = objectValue[moduleValue]; - return appendSymbol; - } - - /// Returns the encoded value - List _getCodeValues(String value) { - final Map codes = _getCodeValue(); - List encodingValue = []; - String givenCharacter = value; - const String startStopCharacter = '101011110'; - const String terminationBar = '1'; - - givenCharacter += _getCheckSum(givenCharacter); - givenCharacter += _getCheckSum(givenCharacter); - - encodingValue.add(startStopCharacter); - encodingValue = _getPatternCollection(givenCharacter, codes, encodingValue); - encodingValue.add(startStopCharacter); - encodingValue.add(terminationBar); - return encodingValue; - } - - /// Retuns the supported input symbol - String _getCode93Character() { - const String code93Character = - '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *\$/+%'; - return code93Character; - } + Code93({int module}) : super(module: module); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean13_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean13_symbology.dart index 945b46aa5..4efa36b44 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean13_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean13_symbology.dart @@ -1,4 +1,4 @@ -part of barcodes; +import '../base/symbology_base.dart'; /// The [EAN13] is based on the [UPCA] standard. As with [UPCA], it supports /// only numeric characters. @@ -24,297 +24,4 @@ class EAN13 extends Symbology { /// automatically. /// EAN13({int module}) : super(module: module); - - /// Represents the encoded input vale - String _encodedValue; - - @override - bool _getIsValidateInput(String value) { - if (value.contains(RegExp(r'^(?=.*?[0-9]).{13}$'))) { - 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.'; - } - } 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.'; - } - return true; - } - - /// This is quite a large method. This method could not be - /// refactored to a smaller methods, since it requires multiple parameters - /// to be passed - @override - void _renderBarcode( - 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(2); - - /// _positions[0] specifies end position of start bar - /// _positions[1] specifies start position of middle bar - /// _positions[2] specifies end position of middle bar - /// _positions[3] specifies start position of end bar - final List positions = List(4); - final Paint paint = _getBarPaint(foregroundColor); - final List code = _getCodeValues(); - final int barTotalLength = _getTotalLength(code); - singleDigitValues[1] = - showValue ? _measureText(_encodedValue[0], textStyle).width : 0; - const int additionalWidth = 2; - singleDigitValues[1] += additionalWidth; - final double width = size.width - singleDigitValues[1]; - double left = module == null - ? offset.dx + singleDigitValues[1] - : _getLeftPosition( - barTotalLength, module, width, offset.dx + singleDigitValues[1]); - final Rect barCodeRect = Rect.fromLTRB( - offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); - double ratio = 0; - if (module != null) { - ratio = module.toDouble(); - } else { - // Calculates the bar length based on number of individual bar codes - final int singleModule = (width ~/ barTotalLength).toInt(); - ratio = singleModule.toDouble(); - final double leftPadding = (width - (barTotalLength * ratio)) / 2; - left += leftPadding; - } - left = left.roundToDouble(); - singleDigitValues[0] = left - singleDigitValues[1]; - for (int i = 0; i < code.length; i++) { - 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 int codeLength = codeValue.length; - for (int j = 0; j < codeLength; j++) { - // Draw the barcode when the current code value is 1 - final bool canDraw = codeValue[j] == '1' ? true : false; - if (canDraw && - (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { - final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + barHeight); - canvas.drawRect(individualBarRect, paint); - } - left += ratio; - if (i == 0 && j == codeLength - 1) { - // Checks the end position of first extra height bar - positions[0] = left; - } else if (i == 1 && j == codeLength - 1) { - // Checks the start position of second extra height bar - positions[1] = left; - } else if (i == 2 && j == codeLength - 1) { - // Checks the end position of second extra height bar - positions[2] = left; - } else if (i == 4 && j == codeLength - 1) { - // Checks the start position of third extra height bar - positions[3] = left; - } - } - } - if (showValue) { - _paintText(canvas, offset, size, _encodedValue, textStyle, textSpacing, - textAlign, positions, singleDigitValues); - } - } - - /// Returns the encoded value - List _getCodeValues() { - const String endBar = '101'; - const String middleBar = '01010'; - final Map structureValue = _getStructure(); - final String structure = - structureValue.entries.elementAt(int.parse(_encodedValue[0])).value; - final List code = []; - code.add(endBar); - String leftString = _encodedValue.substring(1, 7); - code.add(_getLeftValue(true, structure, leftString)); - code.add(middleBar); - leftString = _encodedValue.substring(7, 12); - code.add(_getLeftValue(false, 'RRRRRR', leftString)); - leftString = _encodedValue[12]; - code.add(_getLeftValue(false, 'RRRRRR', leftString)); - code.add(endBar); - return code; - } - - /// To return the binary values of the supported input symbol - Map> _getBinaries() { - return >{ - 'L': [ - // The L (left) type of encoding - '0001101', '0011001', '0010011', '0111101', '0100011', - '0110001', '0101111', '0111011', '0110111', '0001011' - ], - 'G': [ - // The G type of encoding - '0100111', '0110011', '0011011', '0100001', '0011101', - '0111001', '0000101', '0010001', '0001001', '0010111' - ], - 'R': [ - // The R (right) type of encoding - '1110010', '1100110', '1101100', '1000010', '1011100', - '1001110', '1010000', '1000100', '1001000', '1110100' - ], - 'O': [ - // The O (odd) encoding for UPC-E - '0001101', '0011001', '0010011', '0111101', '0100011', - '0110001', '0101111', '0111011', '0110111', '0001011' - ], - 'E': [ - // The E (even) encoding for UPC-E - '0100111', '0110011', '0011011', '0100001', '0011101', - '0111001', '0000101', '0010001', '0001001', '0010111' - ] - }; - } - - /// Calculate total bar length from give input value - int _getTotalLength(List code) { - int count = 0; - for (int i = 0; i < code.length; i++) { - final int numberOfDigits = code[i].length; - count += numberOfDigits; - } - return count; - } - - /// Represents the structure of the supported input symbol - Map _getStructure() { - return { - '0': 'LLLLLL', - '1': 'LLGLGG', - '2': 'LLGGLG', - '3': 'LLGGGL', - '4': 'LGLLGG', - '5': 'LGGLLG', - '6': 'LGGGLL', - '7': 'LGLGLG', - '8': 'LGLGGL', - '9': 'LGGLGL' - }; - } - - /// Method to calculate the check sum digit - int _getCheckSumData(String value) { - 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]) + - int.parse(value[8]) + - int.parse(value[6]) + - int.parse(value[4]) + - int.parse(value[2]) + - int.parse(value[0]); - final int checkSumValue = sum1 + sum2; - final int checkSumDigit = (10 - checkSumValue) % 10; - return checkSumDigit; - } - - /// Method to calculate the left value - String _getLeftValue(bool isLeft, String structure, String leftString) { - String code; - List tempCodes; - final Map> codes = _getBinaries(); - for (int i = 0; i < leftString.length; i++) { - if (structure[i] == 'L') { - tempCodes = codes.entries.elementAt(0).value; - } else if (structure[i] == 'G') { - tempCodes = codes.entries.elementAt(1).value; - } else if (structure[i] == 'R') { - tempCodes = codes.entries.elementAt(2).value; - } else if (structure[i] == 'O') { - tempCodes = codes.entries.elementAt(3).value; - } else if (structure[i] == 'E') { - tempCodes = codes.entries.elementAt(4).value; - } - - final int currentValue = int.parse(leftString[i]); - if (i == 0) { - code = tempCodes[currentValue]; - } else { - code += tempCodes[currentValue]; - } - } - return code; - } - - /// 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) { - final String value1 = value[0]; - final String value2 = value.substring(1, 7); - final String value3 = value.substring(7, 13); - final double secondTextWidth = positions[1] - positions[0]; - final double thirdTextWidth = positions[3] - positions[2]; - - // 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); - - // 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); - - // 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); - } } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean8_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean8_symbology.dart index e8154addb..26ec9341c 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean8_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean8_symbology.dart @@ -1,4 +1,4 @@ -part of barcodes; +import '../base/symbology_base.dart'; /// The [EAN8] is equivalent to the [UPCE] for small packaging details. /// It is shorter than the [EAN13] barcode and longer than [UPCE]. @@ -21,236 +21,4 @@ class EAN8 extends Symbology { /// automatically. /// EAN8({int module}) : super(module: module); - - /// Represents the encoded input value - String _encodedValue; - - @override - bool _getIsValidateInput(String value) { - if (value.contains(RegExp(r'^(?=.*?[0-9]).{8}$'))) { - 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.'; - } - } 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.'; - } - return true; - } - - @override - void _renderBarcode( - 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 - /// _positions[3] specifies start position of end bar - final List positions = List(4); - final Paint paint = _getBarPaint(foregroundColor); - final List code = _getCodeValues(); - final int barTotalLength = _getTotalLength(code); - double left = module == null - ? offset.dx - : _getLeftPosition(barTotalLength, module, size.width, offset.dx); - final Rect barCodeRect = Rect.fromLTRB( - offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); - - double ratio = 0; - if (module != null) { - ratio = module.toDouble(); - } else { - // Calculates the bar length based on number of individual bar codes - final int singleModule = (size.width ~/ barTotalLength).toInt(); - ratio = singleModule.toDouble(); - final double leftPadding = (size.width - (barTotalLength * ratio)) / 2; - left += leftPadding; - } - left = left.roundToDouble(); - for (int i = 0; i < code.length; i++) { - 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 int codeLength = codeValue.length; - for (int j = 0; j < codeLength; j++) { - // Draw the barcode when the current code value is 1 - final bool canDraw = codeValue[j] == '1' ? true : false; - if (canDraw && - (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { - final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + barHeight); - canvas.drawRect(individualBarRect, paint); - } - left += ratio; - if (i == 0 && j == codeLength - 1) { - // Checks the end position of first extra height bar - positions[0] = left; - } else if (i == 1 && j == codeLength - 1) { - // Checks the start position of second extra height bar - positions[1] = left; - } else if (i == 2 && j == codeLength - 1) { - // Checks the end position of second extra height bar - positions[2] = left; - } else if (i == 3 && j == codeLength - 1) { - // Checks the start position of third extra height bar - positions[3] = left; - } - } - } - if (showValue) { - _paintText(canvas, offset, size, _encodedValue, textStyle, textSpacing, - textAlign, positions); - } - } - - /// Calculate total bar length from given input value - int _getTotalLength(List code) { - int count = 0; - for (int i = 0; i < code.length; i++) { - final int numberOfDigits = code[i].length; - count += numberOfDigits; - } - return count; - } - - /// Returns the encoded value - List _getCodeValues() { - const String endBars = '101'; - const String middleBar = '01010'; - Map codes = _getCodeValueRight(true); - final List code = []; - code.add(endBars); - code.add(_getLeftValue(codes, true)); - code.add(middleBar); - codes = _getCodeValueRight(false); - code.add(_getLeftValue(codes, false)); - code.add(endBars); - return code; - } - - /// 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++) { - final int currentValue = int.parse(_encodedValue[i]); - if (i == 0 || i == 4) { - code = codes.entries.elementAt(currentValue).value; - } else { - code += codes.entries.elementAt(currentValue).value; - } - } - return code; - } - - /// Method to calculate the input data - int _getCheckSumData(String value) { - 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 * - (int.parse(value[0]) + - int.parse(value[2]) + - int.parse(value[4]) + - int.parse(value[6])); - final int checkSumValue = sum1 + sum2; - final int checkSumDigit = (10 - checkSumValue) % 10; - return checkSumDigit; - } - return 0; - } - - /// Represents the encoded value for the last 6 digits of the input value - Map _getCodeValueRight(bool isRight) { - Map codes; - if (isRight) { - codes = { - '0': '0001101', - '1': '0011001', - '2': '0010011', - '3': '0111101', - '4': '0100011', - '5': '0110001', - '6': '0101111', - '7': '0111011', - '8': '0110111', - '9': '0001011', - }; - } else { - codes = { - '0': '1110010', - '1': '1100110', - '2': '1101100', - '3': '1000010', - '4': '1011100', - '5': '1001110', - '6': '1010000', - '7': '1000100', - '8': '1001000', - '9': '1110100' - }; - } - return codes; - } - - /// 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) { - final String value1 = value.substring(0, 4); - final String value2 = value.substring(4, 8); - final double firstTextWidth = positions[1] - positions[0]; - final double secondTextWidth = positions[3] - positions[2]; - - // Renders the first four digits of input - _drawText( - 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); - } } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upca_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upca_symbology.dart index 49564254b..30d08fb58 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upca_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upca_symbology.dart @@ -1,4 +1,4 @@ -part of barcodes; +import '../base/symbology_base.dart'; /// The universal product code ([UPCA]) is a numeric symbology used in /// worldwide retail applications. @@ -22,301 +22,4 @@ class UPCA extends Symbology { /// automatically. /// UPCA({int module}) : super(module: module); - - /// Represents the encoded input value - String _encodedValue; - - @override - bool _getIsValidateInput(String value) { - if (value.contains(RegExp(r'^(?=.*?[0-9]).{11}$'))) { - _encodedValue = value + _getCheckSumData(value).toString(); - } else if (value.contains(RegExp(r'^(?=.*?[0-9]).{12}$'))) { - 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.'; - } - } else { - throw 'UPCA supports only numeric characters. ' - 'The provided value should have 11 digits (without check digit) ' - 'or with 12 digits.'; - } - return true; - } - - /// This is quite a large method. This method could not be - /// refactored to a smaller methods, since it requires multiple parameters - /// to be passed - @override - void _renderBarcode( - 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); - const int additionalWidth = 2; - - /// _singleDigitValues[0] specifies left value of start digit - /// _singleDigitValues[1] specifies width of start digit - /// _singleDigitValues[2] specifies left value of end digit - /// _singleDigitValues[3] specifies width of end digit - final List singleDigitValues = List(4); - - /// _positions[0] specifies end position of start bar - /// _positions[1] specifies start position of middle bar - /// _positions[2] specifies end position of middle bar - /// _positions[3] specifies start position of end bar - final List positions = List(4); - if (showValue) { - singleDigitValues[1] = _measureText(_encodedValue[0], textStyle).width; - singleDigitValues[1] += additionalWidth; - singleDigitValues[3] = - _measureText(_encodedValue[_encodedValue.length - 1], textStyle) - .width; - singleDigitValues[3] += additionalWidth; - } else { - singleDigitValues[1] = singleDigitValues[3] = 0; - } - final double width = - size.width - (singleDigitValues[1] + singleDigitValues[3]); - double left = module == null - ? offset.dx + singleDigitValues[1] - : _getLeftPosition( - barTotalLength, module, width, offset.dx + singleDigitValues[1]); - final Rect barCodeRect = Rect.fromLTRB( - offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); - double ratio = 0; - if (module != null) { - ratio = module.toDouble(); - } else { - //Calculates the bar length based on number of individual bar codes - final int singleModule = (width ~/ barTotalLength).toInt(); - ratio = singleModule.toDouble(); - final double leftPadding = (width - (barTotalLength * ratio)) / 2; - left += leftPadding; - } - - left = left.roundToDouble(); - positions[0] = left + ratio; - for (int i = 0; i < code.length; i++) { - 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 int codeLength = codeValue.length; - for (int j = 0; j < codeLength; j++) { - final bool canDraw = codeValue[j] == '1' ? true : false; - if (canDraw && - (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { - final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + barHeight); - canvas.drawRect(individualBarRect, paint); - } - left += ratio; - - if (i == 0 && j == codeLength - 1) { - // Calculates the left value for the first input digit - singleDigitValues[0] = left - singleDigitValues[1]; - } else if (i == 1 && j == codeLength - 1) { - // Finds the end position of first extra height bar - positions[0] = left; - } else if (i == 3 && j == 0) { - // Finds the start position of second extra height bar - positions[1] = left; - } else if (i == 3 && j == codeLength - 1) { - // Finds the end position of second extra height bar - positions[2] = left; - } else if (i == 4 && j == codeLength - 1) { - // Finds the start position of third extra height bar - positions[3] = left; - } else if (i == 5 && j == codeLength - 1) { - // Finds the end position of fourth extra height bar - singleDigitValues[2] = left + additionalWidth; - } - } - } - - if (showValue) { - _paintText(canvas, offset, size, _encodedValue, textStyle, textSpacing, - textAlign, positions, singleDigitValues); - } - } - - /// Calculate total bar length from give input value - int _getTotalLength(List code) { - int count = 0; - for (int i = 0; i < code.length; i++) { - final int numberOfDigits = code[i].length; - count += numberOfDigits; - } - return count; - } - - /// Returns the encoded value - List _getCodeValues() { - const String endDigits = '00000000'; - const String middleBar = '01010'; - final List code = []; - code.add(endDigits); - code.add('101' + _getLeftValue(true, 'L', _encodedValue[0])); - code.add(_getLeftValue(true, 'LLLLL', _encodedValue.substring(1, 6))); - code.add(middleBar); - code.add(_getLeftValue(true, 'RRRRR', _encodedValue.substring(6, 11))); - code.add(_getLeftValue(true, 'R', _encodedValue[11]) + '101'); - code.add(endDigits); - return code; - } - - /// Returns the binary values for the supported input symbol - Map> _getBinaries() { - final Map> codes = >{ - 'L': [ - '0001101', - '0011001', - '0010011', - '0111101', - '0100011', - '0110001', - '0101111', - '0111011', - '0110111', - '0001011' - ], - 'R': [ - '1110010', - '1100110', - '1101100', - '1000010', - '1011100', - '1001110', - '1010000', - '1000100', - '1001000', - '1110100' - ] - }; - - return codes; - } - - /// Returns the encoded value of digits present at left side - String _getLeftValue(bool isLeft, String structure, String leftString) { - String code; - List tempValue; - final Map> codes = _getBinaries(); - for (int i = 0; i < leftString.length; i++) { - if (structure[i] == 'R') { - tempValue = codes.entries.elementAt(1).value; - } else { - tempValue = codes.entries.elementAt(0).value; - } - - final int currentValue = int.parse(leftString[i]); - if (i == 0) { - code = tempValue[currentValue]; - } else { - code += tempValue[currentValue]; - } - } - return code; - } - - /// Method to calculate the check sum digit - int _getCheckSumData(String value) { - 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]) + - int.parse(value[7]) + - int.parse(value[5]) + - int.parse(value[3]) + - int.parse(value[1]); - final int checkSumValue = sum1 + sum2; - return (10 - checkSumValue % 10) % 10; - } - - /// 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) { - final String value1 = value[0]; - final String value2 = value.substring(1, 6); - final String value3 = value.substring(6, 11); - - final double secondTextWidth = positions[1] - positions[0]; - final double thirdTextWidth = positions[3] - positions[2]; - - // 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); - - // 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); - - // 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); - - // 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); - } } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upce_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upce_symbology.dart index dff31bdb7..f97dbc34d 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upce_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upce_symbology.dart @@ -1,4 +1,4 @@ -part of barcodes; +import '../base/symbology_base.dart'; /// As with [UPCA], the [UPCE] symbology supports only numeric characters. /// @@ -17,313 +17,4 @@ class UPCE extends Symbology { /// at the end along with 6 digits of the input product code. /// UPCE({int module}) : super(module: module); - - /// Represents the encoded input value - String _encodedValue; - - @override - bool _getIsValidateInput(String value) { - if (value.contains(RegExp(r'^(?=.*?[0-9]).{6}$'))) { - return true; - } - throw 'UPCE supports only numeric characters. ' - 'The provided value should have 6 digits.'; - } - - /// This is quite a large method. This method could not be - /// refactored to a smaller methods, since it requires multiple parameters - /// to be passed - @override - void _renderBarcode( - 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); - - /// _singleDigitValues[0] specifies left value of start digit - /// _singleDigitValues[1] specifies width of start digit - /// _singleDigitValues[2] specifies left value of end digit - /// _singleDigitValues[3] specifies width of end digit - final List singleDigitValues = List(4); - - /// _positions[0] specifies end position of start bar - /// _positions[1] specifies start position of end bar - final List positions = List(2); - const int additionalWidth = 2; - if (showValue) { - singleDigitValues[1] = _measureText('0', textStyle).width; - singleDigitValues[1] += additionalWidth; - singleDigitValues[3] = - _measureText(_encodedValue[_encodedValue.length - 1], textStyle) - .width; - singleDigitValues[3] += additionalWidth; - } else { - singleDigitValues[1] = singleDigitValues[3] = 0; - } - - final double width = - size.width - (singleDigitValues[1] + singleDigitValues[3]); - double left = module == null - ? offset.dx - : _getLeftPosition( - barTotalLength, module, width, offset.dx + singleDigitValues[1]); - final Rect barCodeRect = Rect.fromLTRB( - offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); - double ratio = 0; - if (module != null) { - ratio = module.toDouble(); - } else { - //Calculates the bar length based on number of individual bar codes - final int singleModule = (width ~/ barTotalLength).toInt(); - ratio = singleModule.toDouble(); - final double leftPadding = (width - (barTotalLength * ratio)) / 2; - left += leftPadding; - } - left = left.roundToDouble(); - 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 int codeLength = codeValue.length; - for (int j = 0; j < codeLength; j++) { - final bool canDraw = codeValue[j] == '1' ? true : false; - if (canDraw && - (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { - final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + barHeight); - canvas.drawRect(individualBarRect, paint); - } - - left += ratio; - - // Calculates the left value for first digit - if (i == 0 && j == codeLength - 1) { - singleDigitValues[0] = left - singleDigitValues[1]; - } else if (i == 1 && j == codeLength - 1) { - // Calculates the start position of intermediate bars - positions[0] = left; - } else if (i == 3 && j == 0) { - // Calculates the end position of intermediate bars - positions[1] = left; - } else if (i == 3 && j == codeLength - 1) { - // Calculates the left value of last digit - singleDigitValues[2] = left + additionalWidth; - } - } - } - if (showValue) { - _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) { - const String value1 = '0'; - final String value2 = value.substring(1, 7); - - final double secondTextWidth = positions[1] - positions[0]; - - // Renders the first digit of encoded value - _drawText( - canvas, - Offset(singleDigitValues[0], offset.dy + size.height + textSpacing), - Size(singleDigitValues[1], size.height), - 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); - // 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); - } - - /// Calculate the total length from given value - int _getTotalLength(List code) { - int count = 0; - for (int i = 0; i < code.length; i++) { - final int numberOfDigits = code[i].length; - count += numberOfDigits; - } - return count; - } - - /// Method to calculate the check sum value - num _getCheckSum(String value) { - num result = 0; - for (int i = 1; i < 11; i += 2) { - result += int.parse(value[i]); - } - for (int i = 0; i < 11; i += 2) { - result += int.parse(value[i]) * 3; - } - return (10 - (result % 10)) % 10; - } - - /// Returns the supported symbol and its pattern value - Map _getStructure() { - final Map upceSymbology = { - '0': 'EEEOOO', - '1': 'EEOEOO', - '2': 'EEOOEO', - '3': 'EEOOOE', - '4': 'EOEEOO', - '5': 'EOOEEO', - '6': 'EOOOEE', - '7': 'EOEOEO', - '8': 'EOEOOE', - '9': 'EOOEOE' - }; - return upceSymbology; - } - - /// Returns the byte value for the supported symbol - List _getValue() { - return [ - 'XX00000XXX', - 'XX10000XXX', - 'XX20000XXX', - 'XXX00000XX', - 'XXXX00000X', - 'XXXXX00005', - 'XXXXX00006', - 'XXXXX00007', - 'XXXXX00008', - 'XXXXX00009' - ]; - } - - /// Returns the value for the last digit - String _getExpansion(String lastDigit) { - final List value = _getValue(); - final int index = int.parse(lastDigit); - return value[index]; - } - - /// Returns the calculated UPC value - String _getUPCValue(String value) { - final String lastDigit = value[value.length - 1]; - final String expansionValue = _getExpansion(lastDigit); - String result = ''; - num index = 0; - for (int i = 0; i < expansionValue.length; i++) { - final String actualValue = expansionValue[i]; - if (actualValue == 'X') { - result += value[index++]; - } else { - result += actualValue; - } - } - result = '0' + result; - String encodingValue = '' + result; - final String checkSumValue = _getCheckSum(result).toString(); - encodingValue += checkSumValue; - _encodedValue = '0' + value + checkSumValue; - return encodingValue; - } - - /// Returns the binary values of the supported symbol - Map> _getBinaries() { - return >{ - 'O': [ - '0001101', - '0011001', - '0010011', - '0111101', - '0100011', - '0110001', - '0101111', - '0111011', - '0110111', - '0001011' - ], - 'E': [ - // The E (even) encoding for UPC-E - '0100111', '0110011', '0011011', '0100001', '0011101', - '0111001', '0000101', '0010001', '0001001', '0010111' - ] - }; - } - - /// Returns the encoded input value - String _encoding(String upceValue, String value, String structure) { - String code = ''; - List tempValue; - final Map> codes = _getBinaries(); - for (int i = 0; i < value.length; i++) { - if (structure[i] == 'E') { - tempValue = codes.entries.elementAt(1).value; - } else { - tempValue = codes.entries.elementAt(0).value; - } - if (i == 0) { - final int index = int.parse(value[i]); - code = tempValue[index]; - } else { - final int index = int.parse(value[i]); - code += tempValue[index]; - } - } - return code; - } - - /// Returns the encoded value - List _getCodeValues(String value) { - const String endBars = '101'; - const String middleBar = '010101'; - const String endDigits = '00000000'; - final List code = []; - final String upceValue = _getUPCValue(value); - final Map structureValue = _getStructure(); - final int actualValue = int.parse(upceValue[upceValue.length - 1]); - final String structure = - structureValue.entries.elementAt(actualValue).value; - code.add(endDigits); - code.add(endBars); - code.add(_encoding(upceValue, value, structure)); - code.add(middleBar); - code.add(endDigits); - return code; - } } 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 new file mode 100644 index 000000000..5b1072cc7 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/codabar_renderer.dart @@ -0,0 +1,143 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../base/symbology_base.dart'; +import 'symbology_base_renderer.dart'; + +/// Represents codabar renderer +class CodabarRenderer extends SymbologyRenderer { + /// Creates codabar renderer class + CodabarRenderer({Symbology symbology}) : super(symbology: symbology) { + _codeBarMap = { + '0': '101010011', + '1': '101011001', + '2': '101001011', + '3': '110010101', + '4': '101101001', + '5': '110101001', + '6': '100101011', + '7': '100101101', + '8': '100110101', + '9': '110100101', + '-': '101001101', + '\$': '101100101', + ':': '1101011011', + '/': '1101101011', + '.': '1101101101', + '+': '101100110011', + 'A': '1011001001', + 'B': '1001001011', + 'C': '1010010011', + 'D': '1010011001' + }; + } + + /// Represents the supported symbol and its byte value + Map _codeBarMap; + + @override + bool getIsValidateInput(String value) { + for (int i = 0; i < value.length; i++) { + if (!_codeBarMap.containsKey(value[i]) || + value[i] == 'A' || + value[i] == 'B' || + value[i] == 'C' || + value[i] == 'D') { + throw 'The provided input cannot be encoded : ' + value[i]; + } + } + return true; + } + + @override + void renderBarcode( + 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); + final Rect barCodeRect = Rect.fromLTRB( + offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); + double ratio = 0; + if (symbology.module != null) { + ratio = symbology.module.toDouble(); + } else { + // Calculates the bar length based on number of individual bar codes + final int singleModule = (size.width ~/ barTotalLength).toInt(); + ratio = singleModule.toDouble(); + final double leftPadding = (size.width - (barTotalLength * ratio)) / 2; + left += leftPadding; + } + left = left.roundToDouble(); + for (int i = 0; i < code.length; i++) { + final String codeValue = code[i]; + const bool hasExtraHeight = false; + final double barHeight = hasExtraHeight + ? size.height + textSize.height + textSpacing + : size.height; + final int codeLength = codeValue.length; + for (int j = 0; j < codeLength; j++) { + final bool canDraw = codeValue[j] == '1' ? true : false; + + // Draws the barcode when the corresponding bar value is one + if (canDraw && + (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { + final Rect individualBarRect = Rect.fromLTRB( + left, offset.dy, left + ratio, offset.dy + barHeight); + canvas.drawRect(individualBarRect, paint); + } + left += ratio; + } + if (i < code.length - 1) { + left += ratio; + } + } + if (showValue) { + drawText(canvas, offset, size, value, textStyle, textSpacing, textAlign); + } + } + + /// Calculate total bar length from give input value + int _getTotalLength(List code) { + int count = 0; + for (int i = 0; i < code.length; i++) { + final int numberOfDigits = code[i].length; + count += numberOfDigits; + } + count += code.length - 1; + return count; + } + + /// Method to append the start and the stop symbol + String _getValueWithStartAndStopSymbol(String value) { + return 'A' + value + 'A'; + } + + /// Returns the encoded value of the provided input value + List _getCodeValues(String value) { + valueWithStartAndStopSymbol = _getValueWithStartAndStopSymbol(value); + final List codeBarValues = + List(valueWithStartAndStopSymbol.length); + for (int i = 0; i < valueWithStartAndStopSymbol.length; i++) { + for (int j = 0; j < _codeBarMap.length; j++) { + if (valueWithStartAndStopSymbol[i] == + _codeBarMap.entries.elementAt(j).key) { + codeBarValues[i] = _codeBarMap.entries.elementAt(j).value; + break; + } + } + } + return codeBarValues; + } +} 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 new file mode 100644 index 000000000..a70e5adf9 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128_renderer.dart @@ -0,0 +1,689 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../base/symbology_base.dart'; +import '../../one_dimensional/code128b_symbology.dart'; +import '../../one_dimensional/code128c_symbology.dart'; +import '../../utils/enum.dart'; +import 'symbology_base_renderer.dart'; + +/// Represents the code128 renderer class +class Code128Renderer extends SymbologyRenderer { + /// Creates the code128 renderer + Code128Renderer({Symbology symbology}) : super(symbology: symbology) { + code128ACharacterSets = []; + + code128ACharacterSets.add(' '); + code128ACharacterSets.add('!'); + code128ACharacterSets.add('"'); + code128ACharacterSets.add('#'); + code128ACharacterSets.add('\$'); + code128ACharacterSets.add('%'); + code128ACharacterSets.add('&'); + code128ACharacterSets.add('\''); + code128ACharacterSets.add('('); + code128ACharacterSets.add(')'); + code128ACharacterSets.add('*'); + code128ACharacterSets.add('+'); + code128ACharacterSets.add(','); + code128ACharacterSets.add('-'); + code128ACharacterSets.add('.'); + code128ACharacterSets.add('/'); + code128ACharacterSets.add('0'); + code128ACharacterSets.add('1'); + code128ACharacterSets.add('2'); + code128ACharacterSets.add('3'); + code128ACharacterSets.add('4'); + code128ACharacterSets.add('5'); + code128ACharacterSets.add('6'); + code128ACharacterSets.add('7'); + code128ACharacterSets.add('8'); + code128ACharacterSets.add('9'); + code128ACharacterSets.add(':'); + code128ACharacterSets.add(';'); + code128ACharacterSets.add('<'); + code128ACharacterSets.add('='); + code128ACharacterSets.add('>'); + code128ACharacterSets.add('?'); + code128ACharacterSets.add('@'); + code128ACharacterSets.add('A'); + code128ACharacterSets.add('B'); + code128ACharacterSets.add('C'); + code128ACharacterSets.add('D'); + code128ACharacterSets.add('E'); + code128ACharacterSets.add('F'); + code128ACharacterSets.add('G'); + code128ACharacterSets.add('H'); + code128ACharacterSets.add('I'); + code128ACharacterSets.add('J'); + code128ACharacterSets.add('K'); + code128ACharacterSets.add('L'); + code128ACharacterSets.add('M'); + code128ACharacterSets.add('N'); + code128ACharacterSets.add('O'); + code128ACharacterSets.add('P'); + code128ACharacterSets.add('Q'); + code128ACharacterSets.add('R'); + code128ACharacterSets.add('S'); + code128ACharacterSets.add('T'); + code128ACharacterSets.add('U'); + code128ACharacterSets.add('V'); + code128ACharacterSets.add('W'); + code128ACharacterSets.add('X'); + code128ACharacterSets.add('Y'); + code128ACharacterSets.add('Z'); + code128ACharacterSets.add('['); + code128ACharacterSets.add('\\'); + code128ACharacterSets.add(']'); + code128ACharacterSets.add('^'); + code128ACharacterSets.add('_'); + code128ACharacterSets.add('\0'); + code128ACharacterSets.add('\u0001'); + code128ACharacterSets.add('\u0002'); + code128ACharacterSets.add('\u0003'); + code128ACharacterSets.add('\u0004'); + code128ACharacterSets.add('\u0005'); + code128ACharacterSets.add('\u0006'); + code128ACharacterSets.add('\a'); + code128ACharacterSets.add('\b'); + code128ACharacterSets.add('\t'); + code128ACharacterSets.add('\n'); + code128ACharacterSets.add('\v'); + code128ACharacterSets.add('\f'); + code128ACharacterSets.add('\r'); + code128ACharacterSets.add('\u000e'); + code128ACharacterSets.add('\u000f'); + code128ACharacterSets.add('\u0010'); + code128ACharacterSets.add('\u0011'); + code128ACharacterSets.add('\u0012'); + code128ACharacterSets.add('\u0013'); + code128ACharacterSets.add('\u0014'); + code128ACharacterSets.add('\u0015'); + code128ACharacterSets.add('\u0016'); + code128ACharacterSets.add('\u0017'); + code128ACharacterSets.add('\u0018'); + code128ACharacterSets.add('\u0019'); + code128ACharacterSets.add('\u001a'); + code128ACharacterSets.add('\u001b'); + code128ACharacterSets.add('\u001c'); + code128ACharacterSets.add('\u001d'); + code128ACharacterSets.add('\u001e'); + code128ACharacterSets.add('\u001f'); + code128ACharacterSets.add('ù'); + code128ACharacterSets.add('ø'); + code128ACharacterSets.add('û'); + code128ACharacterSets.add('ö'); + code128ACharacterSets.add('õ'); + code128ACharacterSets.add('ú'); + code128ACharacterSets.add('÷'); + code128ACharacterSets.add('ü'); + code128ACharacterSets.add('ý'); + code128ACharacterSets.add('þ'); + code128ACharacterSets.add('ÿ'); + + code128BCharacterSets = []; + code128BCharacterSets.add(' '); + code128BCharacterSets.add('!'); + code128BCharacterSets.add('"'); + code128BCharacterSets.add('#'); + code128BCharacterSets.add('\$'); + code128BCharacterSets.add('%'); + code128BCharacterSets.add('&'); + code128BCharacterSets.add('\''); + code128BCharacterSets.add('('); + code128BCharacterSets.add(')'); + code128BCharacterSets.add('*'); + code128BCharacterSets.add('+'); + code128BCharacterSets.add(','); + code128BCharacterSets.add('-'); + code128BCharacterSets.add('.'); + code128BCharacterSets.add('/'); + code128BCharacterSets.add('0'); + code128BCharacterSets.add('1'); + code128BCharacterSets.add('2'); + code128BCharacterSets.add('3'); + code128BCharacterSets.add('4'); + code128BCharacterSets.add('5'); + code128BCharacterSets.add('6'); + code128BCharacterSets.add('7'); + code128BCharacterSets.add('8'); + code128BCharacterSets.add('9'); + code128BCharacterSets.add(':'); + code128BCharacterSets.add(';'); + code128BCharacterSets.add('<'); + code128BCharacterSets.add('='); + code128BCharacterSets.add('>'); + code128BCharacterSets.add('?'); + code128BCharacterSets.add('@'); + code128BCharacterSets.add('A'); + code128BCharacterSets.add('B'); + code128BCharacterSets.add('C'); + code128BCharacterSets.add('D'); + code128BCharacterSets.add('E'); + code128BCharacterSets.add('F'); + code128BCharacterSets.add('G'); + code128BCharacterSets.add('H'); + code128BCharacterSets.add('I'); + code128BCharacterSets.add('J'); + code128BCharacterSets.add('K'); + code128BCharacterSets.add('L'); + code128BCharacterSets.add('M'); + code128BCharacterSets.add('N'); + code128BCharacterSets.add('O'); + code128BCharacterSets.add('P'); + code128BCharacterSets.add('Q'); + code128BCharacterSets.add('R'); + code128BCharacterSets.add('S'); + code128BCharacterSets.add('T'); + code128BCharacterSets.add('U'); + code128BCharacterSets.add('V'); + code128BCharacterSets.add('W'); + code128BCharacterSets.add('X'); + code128BCharacterSets.add('Y'); + code128BCharacterSets.add('Z'); + code128BCharacterSets.add('['); + code128BCharacterSets.add('\\'); + code128BCharacterSets.add(']'); + code128BCharacterSets.add('^'); + code128BCharacterSets.add('_'); + code128BCharacterSets.add('`'); + code128BCharacterSets.add('a'); + code128BCharacterSets.add('b'); + code128BCharacterSets.add('c'); + code128BCharacterSets.add('d'); + code128BCharacterSets.add('e'); + code128BCharacterSets.add('f'); + code128BCharacterSets.add('g'); + code128BCharacterSets.add('h'); + code128BCharacterSets.add('i'); + code128BCharacterSets.add('j'); + code128BCharacterSets.add('k'); + code128BCharacterSets.add('l'); + code128BCharacterSets.add('m'); + code128BCharacterSets.add('n'); + code128BCharacterSets.add('o'); + code128BCharacterSets.add('p'); + code128BCharacterSets.add('q'); + code128BCharacterSets.add('r'); + code128BCharacterSets.add('s'); + code128BCharacterSets.add('t'); + code128BCharacterSets.add('u'); + code128BCharacterSets.add('v'); + code128BCharacterSets.add('w'); + code128BCharacterSets.add('x'); + code128BCharacterSets.add('y'); + code128BCharacterSets.add('z'); + code128BCharacterSets.add('{'); + code128BCharacterSets.add('|'); + code128BCharacterSets.add('}'); + code128BCharacterSets.add('~'); + code128BCharacterSets.add('\u007f'); + code128BCharacterSets.add('ù'); + code128BCharacterSets.add('ø'); + code128BCharacterSets.add('û'); + code128BCharacterSets.add('ö'); + code128BCharacterSets.add('ú'); + code128BCharacterSets.add('ô'); + code128BCharacterSets.add('÷'); + code128BCharacterSets.add('ü'); + code128BCharacterSets.add('ý'); + code128BCharacterSets.add('þ'); + code128BCharacterSets.add('ÿ'); + + code128CCharacterSets = []; + code128CCharacterSets.add('0'); + code128CCharacterSets.add('1'); + code128CCharacterSets.add('2'); + code128CCharacterSets.add('3'); + code128CCharacterSets.add('4'); + code128CCharacterSets.add('5'); + code128CCharacterSets.add('6'); + code128CCharacterSets.add('7'); + code128CCharacterSets.add('8'); + code128CCharacterSets.add('9'); + code128CCharacterSets.add('õ'); + code128CCharacterSets.add('ô'); + code128CCharacterSets.add('÷'); + code128CCharacterSets.add('ü'); + code128CCharacterSets.add('ý'); + code128CCharacterSets.add('þ'); + code128CCharacterSets.add('ÿ'); + } + + /// Represents the start symbol of code128A + static const int _codeAStartSymbol = 103; + + /// Represents the start symbol for Code128B + static const int _codeBStartSymbol = 104; + + /// Represents the start symbol for Code128C + static const int _codeCStartSymbol = 105; + + /// Specifies the value for code128A + static const int _codeA = 101; + + /// Specifies the value for code128B + static const int _codeB = 100; + + /// Specifies the value for code128C + static const int _codeC = 99; + + /// Specifies the stop symbol + static const int _codeStopSymbol = 106; + + /// Represents the index value of FNC1 special character + static const int _codeFNC1 = 102; + + /// Represents the index value of FNC2 special character + static const int _codeFNC2 = 97; + + /// Represents the index value of FNC3 special character + static const int _codeFNC3 = 96; + + /// Represents the index value of FNC4A special character + static const int _codeFNC4A = 101; + + /// Represents the index value of FNC4B special character + static const int _codeFNC4B = 100; + + /// Represents the FNC1 special character + static const String _fnc1 = '\u00f1'; + + /// Represents the FNC2 special character + static const String _fnc2 = '\u00f2'; + + /// Represents the FNC3 special character + static const String _fnc3 = '\u00f3'; + + /// Represents the FNC4 special character + static const String _fnc4 = '\u00f4'; + + /// Represents the supported symbol character of code128A + List code128ACharacterSets; + + /// Represents the supported symbol character of code128B + List code128BCharacterSets; + + /// Represents the supported symbol character of code128C + List code128CCharacterSets; + + /// Returns the byte value of supported symbol + /// + /// This is quite a large method. This method could not be + /// refactored to a smaller methods, since the code value corresponds to this + /// symbology is added into the collection + List> _getCodeValue() { + return >[ + [2, 1, 2, 2, 2, 2], + [2, 2, 2, 1, 2, 2], + [2, 2, 2, 2, 2, 1], + [1, 2, 1, 2, 2, 3], + [1, 2, 1, 3, 2, 2], + [1, 3, 1, 2, 2, 2], + [1, 2, 2, 2, 1, 3], + [1, 2, 2, 3, 1, 2], + [1, 3, 2, 2, 1, 2], + [2, 2, 1, 2, 1, 3], + [2, 2, 1, 3, 1, 2], + [2, 3, 1, 2, 1, 2], + [1, 1, 2, 2, 3, 2], + [1, 2, 2, 1, 3, 2], + [1, 2, 2, 2, 3, 1], + [1, 1, 3, 2, 2, 2], + [1, 2, 3, 1, 2, 2], + [1, 2, 3, 2, 2, 1], + [2, 2, 3, 2, 1, 1], + [2, 2, 1, 1, 3, 2], + [2, 2, 1, 2, 3, 1], + [2, 1, 3, 2, 1, 2], + [2, 2, 3, 1, 1, 2], + [3, 1, 2, 1, 3, 1], + [3, 1, 1, 2, 2, 2], + [3, 2, 1, 1, 2, 2], + [3, 2, 1, 2, 2, 1], + [3, 1, 2, 2, 1, 2], + [3, 2, 2, 1, 1, 2], + [3, 2, 2, 2, 1, 1], + [2, 1, 2, 1, 2, 3], + [2, 1, 2, 3, 2, 1], + [2, 3, 2, 1, 2, 1], + [1, 1, 1, 3, 2, 3], + [1, 3, 1, 1, 2, 3], + [1, 3, 1, 3, 2, 1], + [1, 1, 2, 3, 1, 3], + [1, 3, 2, 1, 1, 3], + [1, 3, 2, 3, 1, 1], + [2, 1, 1, 3, 1, 3], + [2, 3, 1, 1, 1, 3], + [2, 3, 1, 3, 1, 1], + [1, 1, 2, 1, 3, 3], + [1, 1, 2, 3, 3, 1], + [1, 3, 2, 1, 3, 1], + [1, 1, 3, 1, 2, 3], + [1, 1, 3, 3, 2, 1], + [1, 3, 3, 1, 2, 1], + [3, 1, 3, 1, 2, 1], + [2, 1, 1, 3, 3, 1], + [2, 3, 1, 1, 3, 1], + [2, 1, 3, 1, 1, 3], + [2, 1, 3, 3, 1, 1], + [2, 1, 3, 1, 3, 1], + [3, 1, 1, 1, 2, 3], + [3, 1, 1, 3, 2, 1], + [3, 3, 1, 1, 2, 1], + [3, 1, 2, 1, 1, 3], + [3, 1, 2, 3, 1, 1], + [3, 3, 2, 1, 1, 1], + [3, 1, 4, 1, 1, 1], + [2, 2, 1, 4, 1, 1], + [4, 3, 1, 1, 1, 1], + [1, 1, 1, 2, 2, 4], + [1, 1, 1, 4, 2, 2], + [1, 2, 1, 1, 2, 4], + [1, 2, 1, 4, 2, 1], + [1, 4, 1, 1, 2, 2], + [1, 4, 1, 2, 2, 1], + [1, 1, 2, 2, 1, 4], + [1, 1, 2, 4, 1, 2], + [1, 2, 2, 1, 1, 4], + [1, 2, 2, 4, 1, 1], + [1, 4, 2, 1, 1, 2], + [1, 4, 2, 2, 1, 1], + [2, 4, 1, 2, 1, 1], + [2, 2, 1, 1, 1, 4], + [4, 1, 3, 1, 1, 1], + [2, 4, 1, 1, 1, 2], + [1, 3, 4, 1, 1, 1], + [1, 1, 1, 2, 4, 2], + [1, 2, 1, 1, 4, 2], + [1, 2, 1, 2, 4, 1], + [1, 1, 4, 2, 1, 2], + [1, 2, 4, 1, 1, 2], + [1, 2, 4, 2, 1, 1], + [4, 1, 1, 2, 1, 2], + [4, 2, 1, 1, 1, 2], + [4, 2, 1, 2, 1, 1], + [2, 1, 2, 1, 4, 1], + [2, 1, 4, 1, 2, 1], + [4, 1, 2, 1, 2, 1], + [1, 1, 1, 1, 4, 3], + [1, 1, 1, 3, 4, 1], + [1, 3, 1, 1, 4, 1], + [1, 1, 4, 1, 1, 3], + [1, 1, 4, 3, 1, 1], + [4, 1, 1, 1, 1, 3], + [4, 1, 1, 3, 1, 1], + [1, 1, 3, 1, 4, 1], + [1, 1, 4, 1, 3, 1], + [3, 1, 1, 1, 4, 1], + [4, 1, 1, 1, 3, 1], + [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] + ]; + } + + @override + bool getIsValidateInput(String value) { + for (int i = 0; i < value.length; i++) { + final int currentCharacter = value[i].codeUnitAt(0); + if (currentCharacter == _fnc1.codeUnitAt(0) || + currentCharacter == _fnc2.codeUnitAt(0) || + currentCharacter == _fnc3.codeUnitAt(0) || + currentCharacter == _fnc4.codeUnitAt(0)) { + return true; + } else if (currentCharacter < 127) { + return true; + } else { + throw 'The provided input cannot be encoded : ' + value[i]; + } + } + return false; + } + + /// Returns the encoded value + List> _getEncodedValue(String value) { + final List> encodedValue = >[]; + final List> bytes = _getCodeValue(); + int checkDigit = 0; + int weightValue = 1; + int codeTypeValue = 0; + int currentPosition = 0; + while (currentPosition < value.length) { + final int currentCodeType = + _getValidatedCode(currentPosition, codeTypeValue, value); + int currentIndex; + if (currentCodeType == codeTypeValue) { + final int currentValue = value[currentPosition].codeUnitAt(0); + if (currentValue == _fnc1.codeUnitAt(0)) { + currentIndex = _codeFNC1; + } else if (currentValue == _fnc2.codeUnitAt(0)) { + currentIndex = _codeFNC2; + } else if (currentValue == _fnc3.codeUnitAt(0)) { + currentIndex = _codeFNC3; + } else if (currentValue == _fnc4.codeUnitAt(0)) { + if (currentCodeType == _codeA) { + currentIndex = _codeFNC4A; + } else { + currentIndex = _codeFNC4B; + } + } else { + // Calculates the current index value based on code128 type + if (currentCodeType == _codeA) { + currentIndex = + value[currentPosition].codeUnitAt(0) - ' '.codeUnitAt(0); + if (currentIndex < 0) { + currentIndex += '`'.codeUnitAt(0); + } + } else if (currentCodeType == _codeB) { + currentIndex = + value[currentPosition].codeUnitAt(0) - ' '.codeUnitAt(0); + } else { + currentIndex = int.parse( + value.substring(currentPosition, currentPosition + 2)); + currentPosition++; + } + } + currentPosition++; + } else { + currentIndex = _getCurrentIndex(codeTypeValue, currentCodeType); + codeTypeValue = currentCodeType; + } + encodedValue.add(bytes[currentIndex]); + checkDigit += currentIndex * weightValue; + if (currentPosition != 0) { + weightValue++; + } + } + checkDigit %= 103; + encodedValue.add(bytes[checkDigit]); + encodedValue.add(bytes[_codeStopSymbol]); + return encodedValue; + } + + /// Method to get the current index value + int _getCurrentIndex(int codeTypeValue, int currentCodeType) { + int currentIndex; + if (codeTypeValue == 0) { + if (currentCodeType == _codeA) { + currentIndex = _codeAStartSymbol; + } else if (currentCodeType == _codeB) { + currentIndex = _codeBStartSymbol; + } else { + currentIndex = _codeCStartSymbol; + } + } else { + currentIndex = currentCodeType; + } + + return currentIndex; + } + + /// 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); + if (currentCodeType != null) { + return currentCodeType; + } + if (previousCodeSet == _codeB) { + if (codeType == CodeType.fnc1) { + return _codeB; + } + codeType = _getCodeType(start + 2, value); + if (codeType == CodeType.uncodable || codeType == CodeType.singleDigit) { + return _codeB; + } + if (codeType == CodeType.fnc1) { + codeType = _getCodeType(start + 3, value); + if (codeType == CodeType.doubleDigit) { + return this is Code128C ? _codeC : _codeB; + } else { + return _codeB; + } + } + int currentIndex = start + 4; + while (_getCodeType(currentIndex, value) == CodeType.doubleDigit) { + currentIndex += 2; + } + if (codeType == CodeType.singleDigit) { + return _codeB; + } + return this is Code128B ? _codeB : _codeC; + } + + if (codeType == CodeType.fnc1) { + codeType = _getCodeType(start + 1, value); + } + + if (codeType == CodeType.doubleDigit) { + return this is Code128B ? _codeB : _codeC; + } + return _codeB; + } + + /// Method to get the validated types + int _getValidatedCodeTypes( + int start, int previousCodeSet, String value, CodeType codeType) { + if (codeType == CodeType.singleDigit) { + if (previousCodeSet == _codeA) { + return _codeA; + } + return _codeB; + } + + if (codeType == CodeType.uncodable) { + if (start < value.length) { + final int startIndex = value[start].codeUnitAt(0); + if (startIndex < ' '.codeUnitAt(0) || + (previousCodeSet == _codeA && + (startIndex < '`'.codeUnitAt(0) || + (startIndex >= _fnc1.codeUnitAt(0) && + startIndex <= _fnc4.codeUnitAt(0))))) { + return _codeA; + } + } + return _codeB; + } + if (previousCodeSet == _codeA && codeType == CodeType.fnc1) { + return _codeA; + } + if (previousCodeSet == _codeC) { + return _codeC; + } + + return null; + } + + /// Returns the code type based on the input + CodeType _getCodeType(int startIndex, String value) { + final int length = value.length; + if (startIndex >= length) { + return CodeType.uncodable; + } + if (String.fromCharCode(value[startIndex].codeUnitAt(0)) == + String.fromCharCode(_fnc1.codeUnitAt(0))) { + return CodeType.fnc1; + } + if (value[startIndex].codeUnitAt(0) < '0'.codeUnitAt(0) || + value[startIndex].codeUnitAt(0) > '9'.codeUnitAt(0)) { + return CodeType.uncodable; + } + + if (startIndex + 1 >= length) { + return CodeType.singleDigit; + } + + if (value[startIndex + 1].codeUnitAt(0) < '0'.codeUnitAt(0) || + value[startIndex + 1].codeUnitAt(0) > '9'.codeUnitAt(0)) { + return CodeType.singleDigit; + } + + return CodeType.doubleDigit; + } + + @override + void renderBarcode( + 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 ratio = 0; + if (symbology.module != null) { + ratio = symbology.module.toDouble(); + } else { + // Calculates the bar length based on number of individual bar codes + final int singleModule = (size.width ~/ totalBarLength).toInt(); + ratio = singleModule.toDouble(); + final double leftPadding = (size.width - (totalBarLength * ratio)) / 2; + left += leftPadding; + } + left = left.roundToDouble(); + for (int i = 0; i < encodedValue.length; i++) { + bool canDraw = true; + final List currentIndex = encodedValue[i]; + for (int j = 0; j < currentIndex.length; j++) { + final int currentValue = currentIndex[j]; + // Draws the bar code based on the current value + for (int k = 0; k < currentValue; k++) { + if (canDraw) { + final Rect individualBarRect = Rect.fromLTRB( + left, offset.dy, left + ratio, offset.dy + size.height); + canvas.drawRect(individualBarRect, paint); + } + left += ratio; + } + canDraw = !canDraw; + } + } + if (showValue) { + drawText(canvas, offset, size, value, textStyle, textSpacing, textAlign); + } + } + + /// Calculate total bar length from give input value + int _getTotalBarLength(List> encodedValue) { + int length = 0; + for (int i = 0; i < encodedValue.length; i++) { + final List currentValue = encodedValue[i]; + for (int j = 0; j < currentValue.length; j++) { + length += currentValue[j]; + } + } + return length; + } +} 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 new file mode 100644 index 000000000..3fd66405d --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128a_renderer.dart @@ -0,0 +1,18 @@ +import '../../base/symbology_base.dart'; +import 'code128_renderer.dart'; + +/// Represents the code128A renderer class +class Code128ARenderer extends Code128Renderer { + /// Creates the code128A renderer + Code128ARenderer({Symbology symbology}) : super(symbology: symbology); + + @override + 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]; + } + } + 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 new file mode 100644 index 000000000..67e881b17 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128b_renderer.dart @@ -0,0 +1,18 @@ +import '../../base/symbology_base.dart'; +import 'code128_renderer.dart'; + +/// Represents the code128B renderer class +class Code128BRenderer extends Code128Renderer { + /// Creates the code128B renderer + Code128BRenderer({Symbology symbology}) : super(symbology: symbology); + + @override + 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]; + } + } + 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 new file mode 100644 index 000000000..833078870 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128c_renderer.dart @@ -0,0 +1,18 @@ +import '../../base/symbology_base.dart'; +import 'code128_renderer.dart'; + +/// Represents the code128C renderer class +class Code128CRenderer extends Code128Renderer { + /// Creates the code128C renderer + Code128CRenderer({Symbology symbology}) : super(symbology: symbology); + + @override + 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]; + } + } + 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 new file mode 100644 index 000000000..8e03e09ac --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code39_extended_renderer.dart @@ -0,0 +1,166 @@ +import '../../base/symbology_base.dart'; +import 'code39_renderer.dart'; + +/// Represents code39 extended renderer class +class Code39ExtendedRenderer extends Code39Renderer { + /// Creates the code39 extended class + Code39ExtendedRenderer({Symbology symbology}) : super(symbology: symbology) { + _code39ExtendedMap = { + '0': '%U', + '1': '\$A', + '2': '\$B', + '3': '\$C', + '4': '\$D', + '5': '\$E', + '6': '\$F', + '7': '\$G', + '8': '\$H', + '9': '\$I', + '10': '\$J', + '11': '\$K', + '12': '\$L', + '13': '\$M', + '14': '\$N', + '15': '\$O', + '16': '\$P', + '17': '\$Q', + '18': '\$R', + '19': '\$S', + '20': '\$T', + '21': '\$U', + '22': '\$V', + '23': '\$W', + '24': '\$X', + '25': '\$Y', + '26': '\$Z', + '27': '%A', + '28': '%B', + '29': '%C', + '30': '%D', + '31': '%E', + '32': ' ', + '33': '/A', + '34': '/B', + '35': '/C', + '36': '/D', + '37': '/E', + '38': '/F', + '39': '/G', + '40': '/H', + '41': '/I', + '42': '/J', + '43': '/K', + '44': '/L', + '45': '-', + '46': '.', + '47': '/O', + '48': '0', + '49': '1', + '50': '2', + '51': '3', + '52': '4', + '53': '5', + '54': '6', + '55': '7', + '56': '8', + '57': '9', + '58': '/Z', + '59': '%F', + '60': '%G', + '61': '%H', + '62': '%I', + '63': '%J', + '64': '%V', + '65': 'A', + '66': 'B', + '67': 'C', + '68': 'D', + '69': 'E', + '70': 'F', + '71': 'G', + '72': 'H', + '73': 'I', + '74': 'J', + '75': 'K', + '76': 'L', + '77': 'M', + '78': 'N', + '79': 'O', + '80': 'P', + '81': 'Q', + '82': 'R', + '83': 'S', + '84': 'T', + '85': 'U', + '86': 'V', + '87': 'W', + '88': 'X', + '89': 'Y', + '90': 'Z', + '91': '%K', + '92': '%L', + '93': '%M', + '94': '%N', + '95': '%O', + '96': '%W', + '97': '+A', + '98': '+B', + '99': '+C', + '100': '+D', + '101': '+E', + '102': '+F', + '103': '+G', + '104': '+H', + '105': '+I', + '106': '+J', + '107': '+K', + '108': '+L', + '109': '+M', + '110': '+N', + '111': '+O', + '112': '+P', + '113': '+Q', + '114': '+R', + '115': '+S', + '116': '+T', + '117': '+U', + '118': '+V', + '119': '+W', + '120': '+X', + '121': '+Y', + '122': '+Z', + '123': '%P', + '124': '%Q', + '125': ' %R', + '126': '%S', + '127': '%T', + }; + } + + /// Map to stores the input character and its index + Map _code39ExtendedMap; + + /// To validate the provided input value + @override + 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]; + } + } + return true; + } + + /// Returns the encoded byte value for the provided value + @override + List getCodeValues(String value) { + String encodedString = ''; + for (int i = 0; i < value.length; i++) { + final int asciiValue = value[i].codeUnitAt(0); + final String actualValue = + _code39ExtendedMap.entries.elementAt(asciiValue).value; + encodedString += actualValue; + } + return getEncodedValue(encodedString); + } +} 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 new file mode 100644 index 000000000..f30f6b6bd --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code39_renderer.dart @@ -0,0 +1,200 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../base/symbology_base.dart'; +import '../../one_dimensional/code39_symbology.dart'; +import 'symbology_base_renderer.dart'; + +/// Represents the code39 renderer class +class Code39Renderer extends SymbologyRenderer { + /// Creates codabar renderer class + Code39Renderer({Symbology symbology}) : super(symbology: symbology) { + _code39Symbology = [ + '111221211', + '211211112', + '112211112', + '212211111', + '111221112', + '211221111', + '112221111', + '111211212', + '211211211', + '112211211', + '211112112', + '112112112', + '212112111', + '111122112', + '211122111', + '112122111', + '111112212', + '211112211', + '112112211', + '111122211', + '211111122', + '112111122', + '212111121', + '111121122', + '211121121', + '112121121', + '111111222', + '211111221', + '112111221', + '111121221', + '221111112', + '122111112', + '222111111', + '121121112', + '221121111', + '122121111', + '121111212', + '221111211', + '122111211', + '121121211', + '121212111', + '121211121', + '121112121', + '111212121' + ]; + + _character = _getCode39Character(); + } + + /// Represents the code39 symbology + List _code39Symbology; + + /// Represnts the encoded input character + String _character; + + @override + 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]; + } + } + return true; + } + + /// Calculates the check sum value based on the input + String _getCheckSum(String value, String codeBarCharacters) { + int checkSum = 0; + for (int i = 0; i < value.length; i++) { + final int codeNumber = codeBarCharacters.indexOf(value[i]); + checkSum += codeNumber; + } + checkSum = checkSum % 43; + return checkSum.toString(); + } + + /// Returns the provided value with start and stop symbol + String _getValueWithStartAndStopCharacters(String value) { + return '*' + value + '*'; + } + + /// Returns the pattern collection based on the provided input + List _getPatternCollection( + String providedValue, String code39Characters) { + final List code39Values = []; + for (int i = 0; i < providedValue.length; i++) { + final int currentIndex = code39Characters.indexOf(providedValue[i]); + code39Values.add(_code39Symbology[currentIndex]); + } + return code39Values; + } + + @override + void renderBarcode( + 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); + final Rect barCodeRect = Rect.fromLTRB( + offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); + double ratio = 0; + if (symbology.module != null) { + ratio = symbology.module.toDouble(); + } else { + // Calculates the bar length based on number of individual bar codes + final int singleModule = (size.width ~/ barTotalLength).toInt(); + ratio = singleModule.toDouble(); + final double leftPadding = (size.width - (barTotalLength * ratio)) / 2; + left += leftPadding; + } + left = left.roundToDouble(); + for (int i = 0; i < code.length; i++) { + final String codeValue = code[i]; + const bool hasExtraHeight = false; + final double barHeight = hasExtraHeight + ? size.height + textSize.height + textSpacing + : size.height; + final int codeLength = codeValue.length; + for (int j = 0; j < codeLength; j++) { + // The current bar is drawn, if its value is divisible by 2 + final bool canDraw = j % 2 == 0 ? true : false; + final int currentValue = int.parse(codeValue[j]); + if (canDraw && + (left >= barCodeRect.left && + left + (currentValue * ratio) < barCodeRect.right)) { + final Rect individualBarRect = Rect.fromLTRB(left, offset.dy, + left + (currentValue * ratio), offset.dy + barHeight); + canvas.drawRect(individualBarRect, paint); + } + left += currentValue * ratio; + } + if (i < code.length - 1) { + left += ratio; + } + } + if (showValue) { + drawText(canvas, offset, size, value, textStyle, textSpacing, textAlign); + } + } + + /// Returns the encoded value + List getCodeValues(String value) { + return getEncodedValue(value); + } + + /// Calculate total bar length from give input value + int _getTotalLength(List code) { + int count = 0; + for (int i = 0; i < code.length; i++) { + final String currentItem = code[i]; + for (int j = 0; j < currentItem.length; j++) { + count += int.parse(currentItem[j]); + } + } + count += code.length - 1; + return count; + } + + /// Represents the encoded value for provided input + List getEncodedValue(String providedValue) { + final Code39 code39Symbology = symbology; + if (code39Symbology.enableCheckSum) { + final String checkSum = _getCheckSum(providedValue, _character); + providedValue += checkSum; + } + providedValue = _getValueWithStartAndStopCharacters(providedValue); + return _getPatternCollection(providedValue, _character); + } + + /// Represents the code bar value + String _getCode39Character() { + const String code39Character = + '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *\$/+%'; + return code39Character; + } +} 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 new file mode 100644 index 000000000..50ec026da --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code93_renderer.dart @@ -0,0 +1,255 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../base/symbology_base.dart'; +import 'symbology_base_renderer.dart'; + +/// Represents the code93 renderer class +class Code93Renderer extends SymbologyRenderer { + /// Creates the code93 renderer + Code93Renderer({Symbology symbology}) : super(symbology: symbology) { + _character = _getCode93Character(); + } + + /// Represents the input value + String _character; + + @override + 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]; + } + } + return true; + } + + /// Returns the weight for the supported input character + Map _getCharacterWeight() { + return { + '0': '0', + '1': '1', + '2': '2', + '3': '3', + '4': '4', + '5': '5', + '6': '6', + '7': '7', + '8': '8', + '9': '9', + 'A': '10', + 'B': '11', + 'C': '12', + 'D': '13', + 'E': '14', + 'F': '15', + 'G': '16', + 'H': '17', + 'I': '18', + 'J': '19', + 'K': '20', + 'L': '21', + 'M': '22', + 'N': '23', + 'O': '24', + 'P': '25', + 'Q': '26', + 'R': '27', + 'S': '28', + 'T': '29', + 'U': '30', + 'V': '31', + 'W': '32', + 'X': '33', + 'Y': '34', + 'Z': '35', + '-': '36', + '.': '37', + ' ': '38', + '\$': '39', + '/': '40', + '+': '41', + '%': '42', + '(\$)': '43', + '(/)': '44', + '(+)': '45', + '(%)': '46', + }; + } + + /// Returns the byte value of the supported input symbol + Map _getCodeValue() { + return { + '0': '100010100', + '1': '101001000', + '2': '101000100', + '3': '101000010', + '4': '100101000', + '5': '100100100', + '6': '100100010', + '7': '101010000', + '8': '100010010', + '9': '100001010', + 'A': '110101000', + 'B': '110100100', + 'C': '110100010', + 'D': '110010100', + 'E': '110010010', + 'F': '110001010', + 'G': '101101000', + 'H': '101100100', + 'I': '101100010', + 'J': '100110100', + 'K': '100011010', + 'L': '101011000', + 'M': '101001100', + 'N': '101000110', + 'O': '100101100', + 'P': '100010110', + 'Q': '110110100', + 'R': '110110010', + 'S': '110101100', + 'T': '110100110', + 'U': '110010110', + 'V': '110011010', + 'W': '101101100', + 'X': '101100110', + 'Y': '100110110', + 'Z': '100111010', + '-': '100101110', + '.': '111010100', + ' ': '111010010', + '\$': '111001010', + '/': '101101110', + '+': '101110110', + '%': '110101110', + '(\$)': '100100110', + '(/)': '111010110', + '(+)': '100110010', + '(%)': '111011010', + }; + } + + @override + void renderBarcode( + 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); + final Rect barCodeRect = Rect.fromLTRB( + offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); + double ratio = 0; + if (symbology.module != null) { + ratio = symbology.module.toDouble(); + } else { + //Calculates the bar length based on number of individual bar codes + final int singleModule = (size.width ~/ barTotalLength).toInt(); + ratio = singleModule.toDouble(); + final double leftPadding = (size.width - (barTotalLength * ratio)) / 2; + left += leftPadding; + } + left = left.roundToDouble(); + for (int i = 0; i < code.length; i++) { + final String codeValue = code[i]; + const bool hasExtraHeight = false; + final double barHeight = hasExtraHeight + ? size.height + textSize.height + textSpacing + : size.height; + final int codeLength = codeValue.length; + for (int j = 0; j < codeLength; j++) { + //Draws the barcode when the corresponding bar value is one + final bool canDraw = codeValue[j] == '1' ? true : false; + if (canDraw && + (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { + final Rect individualBarRect = Rect.fromLTRB( + left, offset.dy, left + ratio, offset.dy + barHeight); + canvas.drawRect(individualBarRect, paint); + } + left += ratio; + } + } + if (showValue) { + drawText(canvas, offset, size, value, textStyle, textSpacing, textAlign); + } + } + + /// Represents the pattern collection based on the provided input + 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]); + encodingValue.add(codes.entries.elementAt(index).value); + } + return encodingValue; + } + + /// Calculate total bar length from give input value + int _getTotalLength(List code) { + int count = 0; + for (int i = 0; i < code.length; i++) { + final int numberOfDigits = code[i].length; + count += numberOfDigits; + } + return count; + } + + /// Calculates the check sum value + String _getCheckSum(String givenCharacter) { + final String value = givenCharacter; + int weightSum = 0; + int j = 0; + int moduleValue; + String appendSymbol; + final Map codes = _getCharacterWeight(); + final List codeKey = codes.keys.toList(); + for (int i = value.length; i > 0; i--) { + final int index = codeKey.indexOf(value[j]); + final int characterValue = + int.parse(codes.entries.elementAt(index).value) * i; + weightSum += characterValue; + j++; + } + moduleValue = weightSum % 47; + final List objectValue = codes.keys.toList(); + appendSymbol = objectValue[moduleValue]; + return appendSymbol; + } + + /// Returns the encoded value + List _getCodeValues(String value) { + final Map codes = _getCodeValue(); + List encodingValue = []; + String givenCharacter = value; + const String startStopCharacter = '101011110'; + const String terminationBar = '1'; + + givenCharacter += _getCheckSum(givenCharacter); + givenCharacter += _getCheckSum(givenCharacter); + + encodingValue.add(startStopCharacter); + encodingValue = _getPatternCollection(givenCharacter, codes, encodingValue); + encodingValue.add(startStopCharacter); + encodingValue.add(terminationBar); + return encodingValue; + } + + /// Retuns the supported input symbol + String _getCode93Character() { + const String code93Character = + '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *\$/+%'; + return code93Character; + } +} 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 new file mode 100644 index 000000000..f1dcd1fa4 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/ean13_renderer.dart @@ -0,0 +1,305 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../base/symbology_base.dart'; +import '../../utils/helper.dart'; +import 'symbology_base_renderer.dart'; + +/// Represents the EAN13 renderer class +class EAN13Renderer extends SymbologyRenderer { + /// Creates the ean13 renderer + EAN13Renderer({Symbology symbology}) : super(symbology: symbology); + + /// Represents the encoded input vale + String _encodedValue; + + @override + bool getIsValidateInput(String value) { + if (value.contains(RegExp(r'^(?=.*?[0-9]).{13}$'))) { + 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.'; + } + } 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.'; + } + return true; + } + + /// This is quite a large method. This method could not be + /// refactored to a smaller methods, since it requires multiple parameters + /// to be passed + @override + void renderBarcode( + 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(2); + + /// _positions[0] specifies end position of start bar + /// _positions[1] specifies start position of middle bar + /// _positions[2] specifies end position of middle bar + /// _positions[3] specifies start position of end bar + final List positions = List(4); + final Paint paint = getBarPaint(foregroundColor); + final List code = _getCodeValues(); + final int barTotalLength = _getTotalLength(code); + singleDigitValues[1] = + showValue ? measureText(_encodedValue[0], textStyle).width : 0; + const int additionalWidth = 2; + singleDigitValues[1] += additionalWidth; + final double width = size.width - singleDigitValues[1]; + double left = symbology.module == null + ? offset.dx + singleDigitValues[1] + : getLeftPosition(barTotalLength, symbology.module, width, + offset.dx + singleDigitValues[1]); + final Rect barCodeRect = Rect.fromLTRB( + offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); + double ratio = 0; + if (symbology.module != null) { + ratio = symbology.module.toDouble(); + } else { + // Calculates the bar length based on number of individual bar codes + final int singleModule = (width ~/ barTotalLength).toInt(); + ratio = singleModule.toDouble(); + final double leftPadding = (width - (barTotalLength * ratio)) / 2; + left += leftPadding; + } + left = left.roundToDouble(); + singleDigitValues[0] = left - singleDigitValues[1]; + for (int i = 0; i < code.length; i++) { + 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 int codeLength = codeValue.length; + for (int j = 0; j < codeLength; j++) { + // Draw the barcode when the current code value is 1 + final bool canDraw = codeValue[j] == '1' ? true : false; + if (canDraw && + (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { + final Rect individualBarRect = Rect.fromLTRB( + left, offset.dy, left + ratio, offset.dy + barHeight); + canvas.drawRect(individualBarRect, paint); + } + left += ratio; + if (i == 0 && j == codeLength - 1) { + // Checks the end position of first extra height bar + positions[0] = left; + } else if (i == 1 && j == codeLength - 1) { + // Checks the start position of second extra height bar + positions[1] = left; + } else if (i == 2 && j == codeLength - 1) { + // Checks the end position of second extra height bar + positions[2] = left; + } else if (i == 4 && j == codeLength - 1) { + // Checks the start position of third extra height bar + positions[3] = left; + } + } + } + if (showValue) { + _paintText(canvas, offset, size, _encodedValue, textStyle, textSpacing, + textAlign, positions, singleDigitValues); + } + } + + /// Returns the encoded value + List _getCodeValues() { + const String endBar = '101'; + const String middleBar = '01010'; + final Map structureValue = _getStructure(); + final String structure = + structureValue.entries.elementAt(int.parse(_encodedValue[0])).value; + final List code = []; + code.add(endBar); + String leftString = _encodedValue.substring(1, 7); + code.add(_getLeftValue(true, structure, leftString)); + code.add(middleBar); + leftString = _encodedValue.substring(7, 12); + code.add(_getLeftValue(false, 'RRRRRR', leftString)); + leftString = _encodedValue[12]; + code.add(_getLeftValue(false, 'RRRRRR', leftString)); + code.add(endBar); + return code; + } + + /// To return the binary values of the supported input symbol + Map> _getBinaries() { + return >{ + 'L': [ + // The L (left) type of encoding + '0001101', '0011001', '0010011', '0111101', '0100011', + '0110001', '0101111', '0111011', '0110111', '0001011' + ], + 'G': [ + // The G type of encoding + '0100111', '0110011', '0011011', '0100001', '0011101', + '0111001', '0000101', '0010001', '0001001', '0010111' + ], + 'R': [ + // The R (right) type of encoding + '1110010', '1100110', '1101100', '1000010', '1011100', + '1001110', '1010000', '1000100', '1001000', '1110100' + ], + 'O': [ + // The O (odd) encoding for UPC-E + '0001101', '0011001', '0010011', '0111101', '0100011', + '0110001', '0101111', '0111011', '0110111', '0001011' + ], + 'E': [ + // The E (even) encoding for UPC-E + '0100111', '0110011', '0011011', '0100001', '0011101', + '0111001', '0000101', '0010001', '0001001', '0010111' + ] + }; + } + + /// Calculate total bar length from give input value + int _getTotalLength(List code) { + int count = 0; + for (int i = 0; i < code.length; i++) { + final int numberOfDigits = code[i].length; + count += numberOfDigits; + } + return count; + } + + /// Represents the structure of the supported input symbol + Map _getStructure() { + return { + '0': 'LLLLLL', + '1': 'LLGLGG', + '2': 'LLGGLG', + '3': 'LLGGGL', + '4': 'LGLLGG', + '5': 'LGGLLG', + '6': 'LGGGLL', + '7': 'LGLGLG', + '8': 'LGLGGL', + '9': 'LGGLGL' + }; + } + + /// Method to calculate the check sum digit + int _getCheckSumData(String value) { + 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]) + + int.parse(value[8]) + + int.parse(value[6]) + + int.parse(value[4]) + + int.parse(value[2]) + + int.parse(value[0]); + final int checkSumValue = sum1 + sum2; + final int checkSumDigit = (10 - checkSumValue) % 10; + return checkSumDigit; + } + + /// Method to calculate the left value + String _getLeftValue(bool isLeft, String structure, String leftString) { + String code; + List tempCodes; + final Map> codes = _getBinaries(); + for (int i = 0; i < leftString.length; i++) { + if (structure[i] == 'L') { + tempCodes = codes.entries.elementAt(0).value; + } else if (structure[i] == 'G') { + tempCodes = codes.entries.elementAt(1).value; + } else if (structure[i] == 'R') { + tempCodes = codes.entries.elementAt(2).value; + } else if (structure[i] == 'O') { + tempCodes = codes.entries.elementAt(3).value; + } else if (structure[i] == 'E') { + tempCodes = codes.entries.elementAt(4).value; + } + + final int currentValue = int.parse(leftString[i]); + if (i == 0) { + code = tempCodes[currentValue]; + } else { + code += tempCodes[currentValue]; + } + } + return code; + } + + /// 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) { + final String value1 = value[0]; + final String value2 = value.substring(1, 7); + final String value3 = value.substring(7, 13); + final double secondTextWidth = positions[1] - positions[0]; + final double thirdTextWidth = positions[3] - positions[2]; + + // 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); + + // 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); + + // 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); + } +} 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 new file mode 100644 index 000000000..5277264dc --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/ean8_renderer.dart @@ -0,0 +1,244 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../base/symbology_base.dart'; +import 'symbology_base_renderer.dart'; + +/// Represents the EAN8 renderer class +class EAN8Renderer extends SymbologyRenderer { + /// Creates the ean8 renderer + EAN8Renderer({Symbology symbology}) : super(symbology: symbology); + + /// Represents the encoded input value + String _encodedValue; + + @override + bool getIsValidateInput(String value) { + if (value.contains(RegExp(r'^(?=.*?[0-9]).{8}$'))) { + 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.'; + } + } 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.'; + } + return true; + } + + @override + void renderBarcode( + 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 + /// _positions[3] specifies start position of end bar + final List positions = List(4); + 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); + final Rect barCodeRect = Rect.fromLTRB( + offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); + + double ratio = 0; + if (symbology.module != null) { + ratio = symbology.module.toDouble(); + } else { + // Calculates the bar length based on number of individual bar codes + final int singleModule = (size.width ~/ barTotalLength).toInt(); + ratio = singleModule.toDouble(); + final double leftPadding = (size.width - (barTotalLength * ratio)) / 2; + left += leftPadding; + } + left = left.roundToDouble(); + for (int i = 0; i < code.length; i++) { + 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 int codeLength = codeValue.length; + for (int j = 0; j < codeLength; j++) { + // Draw the barcode when the current code value is 1 + final bool canDraw = codeValue[j] == '1' ? true : false; + if (canDraw && + (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { + final Rect individualBarRect = Rect.fromLTRB( + left, offset.dy, left + ratio, offset.dy + barHeight); + canvas.drawRect(individualBarRect, paint); + } + left += ratio; + if (i == 0 && j == codeLength - 1) { + // Checks the end position of first extra height bar + positions[0] = left; + } else if (i == 1 && j == codeLength - 1) { + // Checks the start position of second extra height bar + positions[1] = left; + } else if (i == 2 && j == codeLength - 1) { + // Checks the end position of second extra height bar + positions[2] = left; + } else if (i == 3 && j == codeLength - 1) { + // Checks the start position of third extra height bar + positions[3] = left; + } + } + } + if (showValue) { + _paintText(canvas, offset, size, _encodedValue, textStyle, textSpacing, + textAlign, positions); + } + } + + /// Calculate total bar length from given input value + int _getTotalLength(List code) { + int count = 0; + for (int i = 0; i < code.length; i++) { + final int numberOfDigits = code[i].length; + count += numberOfDigits; + } + return count; + } + + /// Returns the encoded value + List _getCodeValues() { + const String endBars = '101'; + const String middleBar = '01010'; + Map codes = _getCodeValueRight(true); + final List code = []; + code.add(endBars); + code.add(_getLeftValue(codes, true)); + code.add(middleBar); + codes = _getCodeValueRight(false); + code.add(_getLeftValue(codes, false)); + code.add(endBars); + return code; + } + + /// 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++) { + final int currentValue = int.parse(_encodedValue[i]); + if (i == 0 || i == 4) { + code = codes.entries.elementAt(currentValue).value; + } else { + code += codes.entries.elementAt(currentValue).value; + } + } + return code; + } + + /// Method to calculate the input data + int _getCheckSumData(String value) { + 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 * + (int.parse(value[0]) + + int.parse(value[2]) + + int.parse(value[4]) + + int.parse(value[6])); + final int checkSumValue = sum1 + sum2; + final int checkSumDigit = (10 - checkSumValue) % 10; + return checkSumDigit; + } + return 0; + } + + /// Represents the encoded value for the last 6 digits of the input value + Map _getCodeValueRight(bool isRight) { + Map codes; + if (isRight) { + codes = { + '0': '0001101', + '1': '0011001', + '2': '0010011', + '3': '0111101', + '4': '0100011', + '5': '0110001', + '6': '0101111', + '7': '0111011', + '8': '0110111', + '9': '0001011', + }; + } else { + codes = { + '0': '1110010', + '1': '1100110', + '2': '1101100', + '3': '1000010', + '4': '1011100', + '5': '1001110', + '6': '1010000', + '7': '1000100', + '8': '1001000', + '9': '1110100' + }; + } + return codes; + } + + /// 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) { + final String value1 = value.substring(0, 4); + final String value2 = value.substring(4, 8); + final double firstTextWidth = positions[1] - positions[0]; + final double secondTextWidth = positions[3] - positions[2]; + + // Renders the first four digits of input + drawText( + 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); + } +} 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 new file mode 100644 index 000000000..6287e3676 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/symbology_base_renderer.dart @@ -0,0 +1,139 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../base/symbology_base.dart'; +import 'code39_extended_renderer.dart'; +import 'code39_renderer.dart'; +import 'ean13_renderer.dart'; +import 'ean8_renderer.dart'; +import 'upca_renderer.dart'; +import 'upce_renderer.dart'; + +/// Represents the symbology renderer +abstract class SymbologyRenderer { + /// Creates the symbology renderer + SymbologyRenderer({this.symbology}); + + /// Specifies symbology corresponding to this renderer + final Symbology symbology; + + /// Specifies the value with start and the stop symbol + String valueWithStartAndStopSymbol; + + /// Specifies the text size + Size textSize; + + /// Method to valid whether the provided input character is supported + /// by corresponding symbology + bool getIsValidateInput(String value); + + /// 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); + + /// Renders the paint for the bar code + Paint getBarPaint(Color foregroundColor) { + return Paint() + ..color = foregroundColor + ..strokeWidth = 1 + ..style = PaintingStyle.fill; + } + + /// Calculates the left value of the initial bar code + double getLeftPosition( + int barWidth, int module, double width, double offsetX) { + final int calculatedWidth = barWidth * module; + // Calculates the left position of the barcode based on the provided + // module value + double diffInWidth = (width - calculatedWidth) / 2; + diffInWidth += offsetX; + return diffInWidth; + } + + /// 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]) { + final TextSpan span = TextSpan(text: value, style: textStyle); + final TextPainter textPainter = TextPainter( + text: span, textDirection: TextDirection.ltr, textAlign: textAlign); + textPainter.layout(); + double x; + double y; + if ((this is UPCARenderer || + this is EAN13Renderer || + this is UPCERenderer) && + value.length == 1) { + x = offset.dx; + y = offset.dy; + } else { + switch (textAlign) { + case TextAlign.justify: + case TextAlign.center: + { + x = (offset.dx + size.width / 2) - textPainter.width / 2; + y = offset.dy + size.height + textSpacing; + } + break; + case TextAlign.left: + case TextAlign.start: + { + x = offset.dx; + y = offset.dy + size.height + textSpacing; + } + break; + case TextAlign.right: + case TextAlign.end: + { + x = offset.dx + (size.width - textPainter.width); + y = offset.dy + size.height + textSpacing; + } + break; + } + } + + if (this is UPCERenderer || + this is UPCARenderer || + this is EAN8Renderer || + this is EAN13Renderer) { + // Checks whether the calculated x value is present inside the control + // size + if (x >= actualOffset.dx && + x + textPainter.width <= actualOffset.dx + actualSize.width) { + textPainter.paint(canvas, Offset(x, y)); + } + } else { + textPainter.paint(canvas, Offset(x, y)); + } + } + + /// Calculates whether the corresponding type has extra height barcode + bool getHasExtraHeight(int currentItemIndex, List code) { + if (((currentItemIndex == 0 || currentItemIndex == code.length - 1) && + (this is Code39Renderer || this is Code39ExtendedRenderer)) || + ((this is EAN8Renderer || this is EAN13Renderer) && + (currentItemIndex == 0 || + currentItemIndex == 2 || + currentItemIndex == code.length - 1)) || + this is UPCARenderer && + (currentItemIndex == 1 || + currentItemIndex == code.length - 2 || + currentItemIndex == code.length - 4) || + this is UPCERenderer && + (currentItemIndex == 1 || + currentItemIndex == code.length - 2 || + currentItemIndex == code.length - 4)) { + return true; + } else { + return false; + } + } +} 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 new file mode 100644 index 000000000..e71911811 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/upca_renderer.dart @@ -0,0 +1,308 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../base/symbology_base.dart'; +import '../../utils/helper.dart'; +import 'symbology_base_renderer.dart'; + +/// Represents the UPCA renderer class +class UPCARenderer extends SymbologyRenderer { + /// Creates the upca renderer + UPCARenderer({Symbology symbology}) : super(symbology: symbology); + + /// Represents the encoded input value + String _encodedValue; + + @override + bool getIsValidateInput(String value) { + if (value.contains(RegExp(r'^(?=.*?[0-9]).{11}$'))) { + _encodedValue = value + _getCheckSumData(value).toString(); + } else if (value.contains(RegExp(r'^(?=.*?[0-9]).{12}$'))) { + 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.'; + } + } else { + throw 'UPCA supports only numeric characters. ' + 'The provided value should have 11 digits (without check digit) ' + 'or with 12 digits.'; + } + return true; + } + + /// This is quite a large method. This method could not be + /// refactored to a smaller methods, since it requires multiple parameters + /// to be passed + @override + void renderBarcode( + 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); + const int additionalWidth = 2; + + /// _singleDigitValues[0] specifies left value of start digit + /// _singleDigitValues[1] specifies width of start digit + /// _singleDigitValues[2] specifies left value of end digit + /// _singleDigitValues[3] specifies width of end digit + final List singleDigitValues = List(4); + + /// _positions[0] specifies end position of start bar + /// _positions[1] specifies start position of middle bar + /// _positions[2] specifies end position of middle bar + /// _positions[3] specifies start position of end bar + final List positions = List(4); + if (showValue) { + singleDigitValues[1] = measureText(_encodedValue[0], textStyle).width; + singleDigitValues[1] += additionalWidth; + singleDigitValues[3] = + measureText(_encodedValue[_encodedValue.length - 1], textStyle).width; + singleDigitValues[3] += additionalWidth; + } else { + singleDigitValues[1] = singleDigitValues[3] = 0; + } + final double width = + size.width - (singleDigitValues[1] + singleDigitValues[3]); + double left = symbology.module == null + ? offset.dx + singleDigitValues[1] + : getLeftPosition(barTotalLength, symbology.module, width, + offset.dx + singleDigitValues[1]); + final Rect barCodeRect = Rect.fromLTRB( + offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); + double ratio = 0; + if (symbology.module != null) { + ratio = symbology.module.toDouble(); + } else { + //Calculates the bar length based on number of individual bar codes + final int singleModule = (width ~/ barTotalLength).toInt(); + ratio = singleModule.toDouble(); + final double leftPadding = (width - (barTotalLength * ratio)) / 2; + left += leftPadding; + } + + left = left.roundToDouble(); + positions[0] = left + ratio; + for (int i = 0; i < code.length; i++) { + 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 int codeLength = codeValue.length; + for (int j = 0; j < codeLength; j++) { + final bool canDraw = codeValue[j] == '1' ? true : false; + if (canDraw && + (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { + final Rect individualBarRect = Rect.fromLTRB( + left, offset.dy, left + ratio, offset.dy + barHeight); + canvas.drawRect(individualBarRect, paint); + } + left += ratio; + + if (i == 0 && j == codeLength - 1) { + // Calculates the left value for the first input digit + singleDigitValues[0] = left - singleDigitValues[1]; + } else if (i == 1 && j == codeLength - 1) { + // Finds the end position of first extra height bar + positions[0] = left; + } else if (i == 3 && j == 0) { + // Finds the start position of second extra height bar + positions[1] = left; + } else if (i == 3 && j == codeLength - 1) { + // Finds the end position of second extra height bar + positions[2] = left; + } else if (i == 4 && j == codeLength - 1) { + // Finds the start position of third extra height bar + positions[3] = left; + } else if (i == 5 && j == codeLength - 1) { + // Finds the end position of fourth extra height bar + singleDigitValues[2] = left + additionalWidth; + } + } + } + + if (showValue) { + _paintText(canvas, offset, size, _encodedValue, textStyle, textSpacing, + textAlign, positions, singleDigitValues); + } + } + + /// Calculate total bar length from give input value + int _getTotalLength(List code) { + int count = 0; + for (int i = 0; i < code.length; i++) { + final int numberOfDigits = code[i].length; + count += numberOfDigits; + } + return count; + } + + /// Returns the encoded value + List _getCodeValues() { + const String endDigits = '00000000'; + const String middleBar = '01010'; + final List code = []; + code.add(endDigits); + code.add('101' + _getLeftValue(true, 'L', _encodedValue[0])); + code.add(_getLeftValue(true, 'LLLLL', _encodedValue.substring(1, 6))); + code.add(middleBar); + code.add(_getLeftValue(true, 'RRRRR', _encodedValue.substring(6, 11))); + code.add(_getLeftValue(true, 'R', _encodedValue[11]) + '101'); + code.add(endDigits); + return code; + } + + /// Returns the binary values for the supported input symbol + Map> _getBinaries() { + final Map> codes = >{ + 'L': [ + '0001101', + '0011001', + '0010011', + '0111101', + '0100011', + '0110001', + '0101111', + '0111011', + '0110111', + '0001011' + ], + 'R': [ + '1110010', + '1100110', + '1101100', + '1000010', + '1011100', + '1001110', + '1010000', + '1000100', + '1001000', + '1110100' + ] + }; + + return codes; + } + + /// Returns the encoded value of digits present at left side + String _getLeftValue(bool isLeft, String structure, String leftString) { + String code; + List tempValue; + final Map> codes = _getBinaries(); + for (int i = 0; i < leftString.length; i++) { + if (structure[i] == 'R') { + tempValue = codes.entries.elementAt(1).value; + } else { + tempValue = codes.entries.elementAt(0).value; + } + + final int currentValue = int.parse(leftString[i]); + if (i == 0) { + code = tempValue[currentValue]; + } else { + code += tempValue[currentValue]; + } + } + return code; + } + + /// Method to calculate the check sum digit + int _getCheckSumData(String value) { + 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]) + + int.parse(value[7]) + + int.parse(value[5]) + + int.parse(value[3]) + + int.parse(value[1]); + final int checkSumValue = sum1 + sum2; + return (10 - checkSumValue % 10) % 10; + } + + /// 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) { + final String value1 = value[0]; + final String value2 = value.substring(1, 6); + final String value3 = value.substring(6, 11); + + final double secondTextWidth = positions[1] - positions[0]; + final double thirdTextWidth = positions[3] - positions[2]; + + // 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); + + // 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); + + // 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); + + // 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); + } +} 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 new file mode 100644 index 000000000..a4b211858 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/upce_renderer.dart @@ -0,0 +1,320 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../base/symbology_base.dart'; +import '../../utils/helper.dart'; +import 'symbology_base_renderer.dart'; + +/// Represents the UPCE renderer class +class UPCERenderer extends SymbologyRenderer { + /// Creates the upce renderer + UPCERenderer({Symbology symbology}) : super(symbology: symbology); + + /// Represents the encoded input value + String _encodedValue; + + @override + bool getIsValidateInput(String value) { + if (value.contains(RegExp(r'^(?=.*?[0-9]).{6}$'))) { + return true; + } + throw 'UPCE supports only numeric characters. ' + 'The provided value should have 6 digits.'; + } + + /// This is quite a large method. This method could not be + /// refactored to a smaller methods, since it requires multiple parameters + /// to be passed + @override + void renderBarcode( + 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); + + /// _singleDigitValues[0] specifies left value of start digit + /// _singleDigitValues[1] specifies width of start digit + /// _singleDigitValues[2] specifies left value of end digit + /// _singleDigitValues[3] specifies width of end digit + final List singleDigitValues = List(4); + + /// _positions[0] specifies end position of start bar + /// _positions[1] specifies start position of end bar + final List positions = List(2); + const int additionalWidth = 2; + if (showValue) { + singleDigitValues[1] = measureText('0', textStyle).width; + singleDigitValues[1] += additionalWidth; + singleDigitValues[3] = + measureText(_encodedValue[_encodedValue.length - 1], textStyle).width; + singleDigitValues[3] += additionalWidth; + } else { + singleDigitValues[1] = singleDigitValues[3] = 0; + } + + final double width = + size.width - (singleDigitValues[1] + singleDigitValues[3]); + double left = symbology.module == null + ? offset.dx + : getLeftPosition(barTotalLength, symbology.module, width, + offset.dx + singleDigitValues[1]); + final Rect barCodeRect = Rect.fromLTRB( + offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); + double ratio = 0; + if (symbology.module != null) { + ratio = symbology.module.toDouble(); + } else { + //Calculates the bar length based on number of individual bar codes + final int singleModule = (width ~/ barTotalLength).toInt(); + ratio = singleModule.toDouble(); + final double leftPadding = (width - (barTotalLength * ratio)) / 2; + left += leftPadding; + } + left = left.roundToDouble(); + 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 int codeLength = codeValue.length; + for (int j = 0; j < codeLength; j++) { + final bool canDraw = codeValue[j] == '1' ? true : false; + if (canDraw && + (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { + final Rect individualBarRect = Rect.fromLTRB( + left, offset.dy, left + ratio, offset.dy + barHeight); + canvas.drawRect(individualBarRect, paint); + } + + left += ratio; + + // Calculates the left value for first digit + if (i == 0 && j == codeLength - 1) { + singleDigitValues[0] = left - singleDigitValues[1]; + } else if (i == 1 && j == codeLength - 1) { + // Calculates the start position of intermediate bars + positions[0] = left; + } else if (i == 3 && j == 0) { + // Calculates the end position of intermediate bars + positions[1] = left; + } else if (i == 3 && j == codeLength - 1) { + // Calculates the left value of last digit + singleDigitValues[2] = left + additionalWidth; + } + } + } + if (showValue) { + _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) { + const String value1 = '0'; + final String value2 = value.substring(1, 7); + + final double secondTextWidth = positions[1] - positions[0]; + + // Renders the first digit of encoded value + drawText( + canvas, + Offset(singleDigitValues[0], offset.dy + size.height + textSpacing), + Size(singleDigitValues[1], size.height), + 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); + // 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); + } + + /// Calculate the total length from given value + int _getTotalLength(List code) { + int count = 0; + for (int i = 0; i < code.length; i++) { + final int numberOfDigits = code[i].length; + count += numberOfDigits; + } + return count; + } + + /// Method to calculate the check sum value + num _getCheckSum(String value) { + num result = 0; + for (int i = 1; i < 11; i += 2) { + result += int.parse(value[i]); + } + for (int i = 0; i < 11; i += 2) { + result += int.parse(value[i]) * 3; + } + return (10 - (result % 10)) % 10; + } + + /// Returns the supported symbol and its pattern value + Map _getStructure() { + final Map upceSymbology = { + '0': 'EEEOOO', + '1': 'EEOEOO', + '2': 'EEOOEO', + '3': 'EEOOOE', + '4': 'EOEEOO', + '5': 'EOOEEO', + '6': 'EOOOEE', + '7': 'EOEOEO', + '8': 'EOEOOE', + '9': 'EOOEOE' + }; + return upceSymbology; + } + + /// Returns the byte value for the supported symbol + List _getValue() { + return [ + 'XX00000XXX', + 'XX10000XXX', + 'XX20000XXX', + 'XXX00000XX', + 'XXXX00000X', + 'XXXXX00005', + 'XXXXX00006', + 'XXXXX00007', + 'XXXXX00008', + 'XXXXX00009' + ]; + } + + /// Returns the value for the last digit + String _getExpansion(String lastDigit) { + final List value = _getValue(); + final int index = int.parse(lastDigit); + return value[index]; + } + + /// Returns the calculated UPC value + String _getUPCValue(String value) { + final String lastDigit = value[value.length - 1]; + final String expansionValue = _getExpansion(lastDigit); + String result = ''; + num index = 0; + for (int i = 0; i < expansionValue.length; i++) { + final String actualValue = expansionValue[i]; + if (actualValue == 'X') { + result += value[index++]; + } else { + result += actualValue; + } + } + result = '0' + result; + String encodingValue = '' + result; + final String checkSumValue = _getCheckSum(result).toString(); + encodingValue += checkSumValue; + _encodedValue = '0' + value + checkSumValue; + return encodingValue; + } + + /// Returns the binary values of the supported symbol + Map> _getBinaries() { + return >{ + 'O': [ + '0001101', + '0011001', + '0010011', + '0111101', + '0100011', + '0110001', + '0101111', + '0111011', + '0110111', + '0001011' + ], + 'E': [ + // The E (even) encoding for UPC-E + '0100111', '0110011', '0011011', '0100001', '0011101', + '0111001', '0000101', '0010001', '0001001', '0010111' + ] + }; + } + + /// Returns the encoded input value + String _encoding(String upceValue, String value, String structure) { + String code = ''; + List tempValue; + final Map> codes = _getBinaries(); + for (int i = 0; i < value.length; i++) { + if (structure[i] == 'E') { + tempValue = codes.entries.elementAt(1).value; + } else { + tempValue = codes.entries.elementAt(0).value; + } + if (i == 0) { + final int index = int.parse(value[i]); + code = tempValue[index]; + } else { + final int index = int.parse(value[i]); + code += tempValue[index]; + } + } + return code; + } + + /// Returns the encoded value + List _getCodeValues(String value) { + const String endBars = '101'; + const String middleBar = '010101'; + const String endDigits = '00000000'; + final List code = []; + final String upceValue = _getUPCValue(value); + final Map structureValue = _getStructure(); + final int actualValue = int.parse(upceValue[upceValue.length - 1]); + final String structure = + structureValue.entries.elementAt(actualValue).value; + code.add(endDigits); + code.add(endBars); + code.add(_encoding(upceValue, value, structure)); + code.add(middleBar); + code.add(endDigits); + return code; + } +} 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 new file mode 100644 index 000000000..0f6d2ca18 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/datamatrix_renderer.dart @@ -0,0 +1,1173 @@ +import 'dart:convert' show utf8; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_barcodes/barcodes.dart'; +import '../../base/symbology_base.dart'; +import '../../utils/enum.dart'; +import '../../utils/helper.dart'; +import '../one_dimensional/symbology_base_renderer.dart'; + +/// Represents the data matrix renderer class +class DataMatrixRenderer extends SymbologyRenderer { + /// Creates the data matrix renderer + DataMatrixRenderer({Symbology symbology}) : super(symbology: symbology) { + _dataMatrixSymbology = symbology; + _initialize(); + } + + DataMatrix _dataMatrixSymbology; + + /// Defines the list of symbol attributes + List<_DataMatrixSymbolAttribute> _symbolAttributes; + + /// Defines the log list + List _log; + + /// Defines the aLog list + List _aLog; + + /// Defines the block length + int _blockLength; + + /// Defines the polynomial collection + List _polynomial; + + /// Defines the symbol attributes + _DataMatrixSymbolAttribute _symbolAttribute; + + /// Defines the actual encoding value + DataMatrixEncoding _encoding; + + /// Defines the data matrix input value + String _inputValue; + + /// Defines the list of data matrix + List> _dataMatrixList; + + /// Defines the list of encoded code words + List _encodedCodeword; + + /// Initializes the symbol attributes + /// + /// This is deliberately a very large method. This method could not be + /// refactored to a smaller methods, since the attributes corresponds to the + /// each matrix is added into the list + void _initialize() { + _symbolAttributes = <_DataMatrixSymbolAttribute>[ + _DataMatrixSymbolAttribute( + 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), + _DataMatrixSymbolAttribute( + 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), + _DataMatrixSymbolAttribute( + 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), + _DataMatrixSymbolAttribute( + 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), + _DataMatrixSymbolAttribute( + 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), + _DataMatrixSymbolAttribute( + 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), + _DataMatrixSymbolAttribute( + 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), + _DataMatrixSymbolAttribute( + 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), + _DataMatrixSymbolAttribute( + 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), + _DataMatrixSymbolAttribute( + 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), + _DataMatrixSymbolAttribute( + 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), + _DataMatrixSymbolAttribute( + 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), + + // Rectangle matrix + _DataMatrixSymbolAttribute( + 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), + _DataMatrixSymbolAttribute( + 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), + _DataMatrixSymbolAttribute( + 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) + ]; + + _createLogList(); + } + + /// Method to create the log list + void _createLogList() { + _log = List(256); + _aLog = List(256); + _log[0] = -255; + _aLog[0] = 1; + + for (int i = 1; i <= 255; i++) { + _aLog[i] = _aLog[i - 1] * 2; + + if (_aLog[i] >= 256) { + _aLog[i] = _aLog[i] ^ 301; + } + + _log[_aLog[i]] = i; + } + } + + /// method to encode the provided data + List _getData() { + return List.from(utf8.encode(_inputValue)); + } + + /// Method used to create the data matrix + void _createMatrix(List codeword) { + int x, y, numOfRows, numOfColumns; + List places; + final int width = _symbolAttribute.symbolColumn; + final int height = _symbolAttribute.symbolRow; + final int fieldWidth = width ~/ _symbolAttribute.horizontalDataRegion; + final int fieldHeight = height ~/ _symbolAttribute.verticalDataRegion; + numOfColumns = width - 2 * (width ~/ fieldWidth); + numOfRows = height - 2 * (height ~/ fieldHeight); + places = List(numOfColumns * numOfRows); + + _errorCorrectingCode200Placement(places, numOfRows, numOfColumns); + final List matrix = List(width * height); + for (y = 0; y < height; y += fieldHeight) { + for (x = 0; x < width; x++) { + matrix[y * width + x] = 1; + } + + for (x = 0; x < width; x += 2) { + matrix[(y + fieldHeight - 1) * width + x] = 1; + } + } + + for (x = 0; x < width; x += fieldWidth) { + for (y = 0; y < height; y++) { + matrix[y * width + x] = 1; + } + + for (y = 0; y < height; y += 2) { + matrix[y * width + x + fieldWidth - 1] = 1; + } + } + + for (y = 0; y < numOfRows; y++) { + for (x = 0; x < numOfColumns; x++) { + final int v = places[(numOfRows - y - 1) * numOfColumns + x]; + if (v == 1 || v > 7 && (codeword[(v >> 3) - 1] & (1 << (v & 7))) != 0) { + matrix[(1 + y + 2 * (y ~/ (fieldHeight - 2))) * width + + 1 + + x + + 2 * (x ~/ (fieldWidth - 2))] = 1; + } + } + } + + _createArray(matrix); + } + + /// Methods to create array based on row and column + void _createArray(List matrix) { + final int symbolColumn = _symbolAttribute.symbolColumn, + symbolRow = _symbolAttribute.symbolRow; + + final List> tempArray = + List>.generate(symbolColumn, (int j) => List(symbolRow)); + + for (int m = 0; m < symbolColumn; m++) { + for (int n = 0; n < symbolRow; n++) { + tempArray[m][n] = matrix[symbolColumn * n + m]; + } + } + + final List> tempArray2 = + List>.generate(symbolRow, (int j) => List(symbolColumn)); + + for (int i = 0; i < symbolRow; i++) { + for (int j = 0; j < symbolColumn; j++) { + tempArray2[symbolRow - 1 - i][j] = tempArray[j][i]; + } + } + + _addQuietZone(tempArray2); + } + + /// Method used to add the quiet zone + void _addQuietZone(List> tempArray) { + const int quietZone = 1; + final int w = _symbolAttribute.symbolRow + (2 * quietZone); + final int h = _symbolAttribute.symbolColumn + (2 * quietZone); + _dataMatrixList = List>.generate(w, (int j) => List(h)); + // Top quietzone. + for (int i = 0; i < h; i++) { + _dataMatrixList[0][i] = 0; + } + + for (int i = quietZone; i < w - quietZone; i++) { + _dataMatrixList[i][0] = 0; + + for (int j = quietZone; j < h - quietZone; j++) { + _dataMatrixList[i][j] = tempArray[i - quietZone][j - quietZone]; + } + + // Right quietzone. + _dataMatrixList[i][h - quietZone] = 0; + } + + for (int i = 0; i < h; i++) { + _dataMatrixList[w - quietZone][i] = 0; + } + } + + /// Method to encode the error correcting code word + 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); + } + + if (column < 0) { + column += numOfColumns; + row += 4 - ((numOfColumns + 4) % 8); + } + + array[row * numOfColumns + column] = (place << 3) + character.codeUnitAt(0); + } + + /// 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)); + } + + /// 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)); + } + + /// 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)); + } + + /// 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)); + } + + /// 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)); + } + + /// Method to encode the error correcting code word + void _errorCorrectingCode200Placement( + List array, int numOfRows, int numOfColumns) { + int row, column, place; + for (row = 0; row < numOfRows; row++) { + for (column = 0; column < numOfColumns; column++) { + array[row * numOfColumns + column] = 0; + } + } + + place = 1; + row = 4; + column = 0; + do { + if (row == numOfRows && !(column != 0)) { + _errorCorrectingCode200PlacementCornerA( + array, numOfRows, numOfColumns, place++); + } + + if ((row == numOfRows - 2) && + !(column != 0) && + ((numOfColumns % 4) != 0)) { + _errorCorrectingCode200PlacementCornerB( + array, numOfRows, numOfColumns, place++); + } + + if (row == numOfRows - 2 && !(column != 0) && (numOfColumns % 8) == 4) { + _errorCorrectingCode200PlacementCornerC( + array, numOfRows, numOfColumns, place++); + } + + if (row == numOfRows + 4 && column == 2 && !((numOfColumns % 8) != 0)) { + _errorCorrectingCode200PlacementCornerD( + array, numOfRows, numOfColumns, place++); + } + // enocoding placement (up/right) + do { + if (row < numOfRows && + column >= 0 && + !(array[row * numOfColumns + column] != 0)) { + _errorCorrectingCode200PlacementBlock( + array, numOfRows, numOfColumns, row, column, place++); + } + + row -= 2; + column += 2; + } while (row >= 0 && column < numOfColumns); + row++; + column += 3; + // enocoding placement (down/left) + do { + if (row >= 0 && + column < numOfColumns && + !(array[row * numOfColumns + column] != 0)) { + _errorCorrectingCode200PlacementBlock( + array, numOfRows, numOfColumns, row, column, place++); + } + + row += 2; + column -= 2; + } while (row < numOfRows && column >= 0); + row += 3; + column++; + } while (row < numOfRows || column < numOfColumns); + if (!(array[numOfRows * numOfColumns - 1] != 0)) { + array[numOfRows * numOfColumns - 1] = + array[numOfRows * numOfColumns - numOfColumns - 2] = 1; + } + } + + /// Method to double the error correcting code + int _getErrorCorrectingCodeDoublify(int i, int j) { + if (i == 0) { + return 0; + } + + if (j == 0) { + return i; + } + + return _aLog[(_log[i] + j) % 255]; + } + + /// Method used for error correction + int _getErrorCorrectingCodeProduct(int i, int j) { + if (i == 0 || j == 0) { + return 0; + } else if (i > 255 || j > 255) { + return 0; + } + return _aLog[(_log[i] + _log[j]) % 255]; + } + + /// Method to perform the XOR operation for error correction + int _getErrorCorrectingCodeSum(int i, int j) { + return i ^ j; + } + + /// Method to pad the code word + List _getPaddedCodewords(int dataCWLength, List temp) { + final List codewords = []; + int length = temp.length; + for (int i = 0; i < length; i++) { + codewords.add(temp[i]); + } + + if (length < dataCWLength) { + codewords.add(129); + } + + length = codewords.length; + while (length < dataCWLength) { + int _value = 129 + (((length + 1) * 149) % 253) + 1; + if (_value > 254) { + _value -= 254; + } + + codewords.add(_value); + length = codewords.length; + } + + return codewords; + } + + /// Method used for base256 encoding + List _getDataMatrixBaseEncoder(List dataCodeword) { + int num = 1; + if (dataCodeword.length > 249) { + num++; + } + + final List result = List((1 + num) + dataCodeword.length); + result[0] = 231; + if (dataCodeword.length <= 249) { + result[1] = dataCodeword.length; + } else { + result[1] = ((dataCodeword.length / 250) + 249).toInt(); + result[2] = dataCodeword.length % 250; + } + + for (int i = 0; i < dataCodeword.length; i++) { + result[(1 + num) + i] = dataCodeword[i]; + } + + for (int i = 1; i < result.length; i++) { + result[i] = _getBase256Codeword(result[i], i); + } + + return result; + } + + /// Method to compute the code word + int _getBase256Codeword(int codewordValue, int index) { + final int i = ((149 * (index + 1)) % 255) + 1; + final int j = codewordValue + i; + if (j <= 255) { + return j; + } + + return j - 256; + } + + /// Method used for ASCII numeric encoding + List _getDataMatrixASCIINumericEncoder(List dataCodeword) { + List destinationArray = dataCodeword; + bool isEven = true; + // if the input data length is odd, add 0 in front of the data. + if ((destinationArray.length % 2) == 1) { + isEven = false; + destinationArray = List(dataCodeword.length + 1); + for (int i = 0; i < dataCodeword.length; i++) { + destinationArray[i] = dataCodeword[i]; + } + } + + final List result = List(destinationArray.length ~/ 2); + 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) + + (destinationArray[(2 * i) + 1] - 48)) + + 130; + } + } + return result; + } + + /// Method used for ASCII encoding + List _getDataMatrixASCIIEncoder(List dataCodeword) { + final List result = List.from(dataCodeword); + int index = 0; + for (int i = 0; i < dataCodeword.length; i++) { + if (dataCodeword[i] >= 48 && dataCodeword[i] <= 57) { + int prevIndex = 0; + if (i != 0) { + prevIndex = index - 1; + } + + final int prevValue = result[prevIndex] - 1; + int priorValue = 0; + if (i != 0 && index != 1) { + priorValue = result[prevIndex - 1]; + } + + //Check the priorvalue is digit or non convertable value.if it is true + // then combine the 2 digit + if (priorValue != 235 && prevValue >= 48 && prevValue <= 57) { + result[prevIndex] = + 10 * (prevValue - 0) + (dataCodeword[i] - 0) + 130; + } else { + result[index++] = dataCodeword[i] + 1; + } + } else if (dataCodeword[i] < 127) { + result[index++] = dataCodeword[i] + 1; + } else { + //Assign 235 default value for non + // convertable values(other than digits,letters and special characters + // 0 to 127 asciii values) + result[index] = 235; + result[index++] = dataCodeword[i] - 127; + } + } + + final List encodedData = List.from(result); + return encodedData; + } + + /// Method used for computing error correction + List _getComputedErrorCorrection(List codeword) { + _setSymbolAttribute(codeword); + final int numCorrectionCodeword = _symbolAttribute.correctionCodeWords; + // Create error correction array. + final List correctionCodeWordArray = + List(numCorrectionCodeword + _symbolAttribute.dataCodeWords); + for (int i = 0; i < correctionCodeWordArray.length; i++) { + correctionCodeWordArray[i] = 0; + } + + final int step = _symbolAttribute.interLeavedBlock; + final int symbolDataWords = _symbolAttribute.dataCodeWords; + final int blockErrorWords = _symbolAttribute.correctionCodeWords ~/ step; + final int total = symbolDataWords + blockErrorWords * step; + // Updates m_polynomial based on the required number of correction bytes. + _createPolynomial(step); + //set block length (each block consists of length 68 ) + _blockLength = 68; + final List blockByte = List(_blockLength); + for (int block = 0; block < step; block++) { + for (int k = 0; k < blockByte.length; k++) { + blockByte[k] = 0; + } + + for (int i = block; i < symbolDataWords; i += step) { + final int val = _getErrorCorrectingCodeSum( + blockByte[blockErrorWords - 1], _encodedCodeword[i]); + for (int j = blockErrorWords - 1; j > 0; j--) { + blockByte[j] = _getErrorCorrectingCodeSum(blockByte[j - 1], + _getErrorCorrectingCodeProduct(_polynomial[j], val)); + } + + blockByte[0] = _getErrorCorrectingCodeProduct(_polynomial[0], val); + } + + int blockDataWords = 0; + if (block >= 8 && + _dataMatrixSymbology.dataMatrixSize == DataMatrixSize.size144x144) { + blockDataWords = _symbolAttribute.dataCodeWords ~/ step; + } else { + blockDataWords = _symbolAttribute.interLeavedDataBlock; + + int bIndex = blockErrorWords; + + for (int i = block + (step * blockDataWords); i < total; i += step) { + correctionCodeWordArray[i] = blockByte[--bIndex]; + } + + if (bIndex != 0) { + throw 'Error in error correction code generation!'; + } + } + } + + return _getCorrectionCodeWordArray( + correctionCodeWordArray, numCorrectionCodeword); + } + + /// Method to get the correction code word + List _getCorrectionCodeWordArray( + List correctionCodeWordArray, int numCorrectionCodeword) { + if (correctionCodeWordArray.length > numCorrectionCodeword) { + final List tmp = correctionCodeWordArray; + correctionCodeWordArray = List(numCorrectionCodeword); + for (int i = 0; i < correctionCodeWordArray.length; i++) { + correctionCodeWordArray[i] = 0; + } + int z = 0; + + for (int i = tmp.length - 1; i > _symbolAttribute.dataCodeWords; i--) { + correctionCodeWordArray[z++] = tmp[i]; + } + } + + final List reversedList = correctionCodeWordArray.reversed.toList(); + return reversedList; + } + + /// Method to set the symbol attribute + void _setSymbolAttribute(List codeword) { + int dataLength = codeword.length; + _symbolAttribute = _DataMatrixSymbolAttribute(); + if (_dataMatrixSymbology.dataMatrixSize == DataMatrixSize.auto) { + for (int i = 0; i < _symbolAttributes.length; i++) { + final _DataMatrixSymbolAttribute attribute = _symbolAttributes[i]; + if (attribute.dataCodeWords >= dataLength) { + _symbolAttribute = attribute; + break; + } + } + } else { + _symbolAttribute = _symbolAttributes[ + getDataMatrixSize(_dataMatrixSymbology.dataMatrixSize) - 1]; + } + + List temp; + // Pad data codeword if the length is less than the selected symbol + // attribute. + if (_symbolAttribute.dataCodeWords > dataLength) { + temp = _getPaddedCodewords(_symbolAttribute.dataCodeWords, codeword); + _encodedCodeword = List.from(temp); + dataLength = codeword.length; + } else if (_symbolAttribute.dataCodeWords == 0) { + throw 'Data cannot be encoded as barcode'; + } else if (_symbolAttribute.dataCodeWords < dataLength) { + final String symbolRow = _symbolAttribute.symbolRow.toString(); + final String symbolColumn = _symbolAttribute.symbolColumn.toString(); + throw 'Data too long for $symbolRow x $symbolColumn barcode.'; + } + } + + /// Method used to create the polynomial + void _createPolynomial(int step) { + //Set block length for polynomial values + _blockLength = 69; + _polynomial = List(_blockLength); + final int blockErrorWords = _symbolAttribute.correctionCodeWords ~/ step; + for (int i = 0; i < _polynomial.length; i++) { + _polynomial[i] = 0x01; + } + + for (int i = 1; i <= blockErrorWords; i++) { + for (int j = i - 1; j >= 0; j--) { + _polynomial[j] = _getErrorCorrectingCodeDoublify(_polynomial[j], i); + if (j > 0) { + _polynomial[j] = + _getErrorCorrectingCodeSum(_polynomial[j], _polynomial[j - 1]); + } + } + } + } + + /// Method to get the code word + List _getCodeword(List dataCodeword) { + _encodedCodeword = _getDataCodeword(dataCodeword); + final List correctCodeword = + _getComputedErrorCorrection(_encodedCodeword); + final List finalCodeword = + List(_encodedCodeword.length + correctCodeword.length); + for (int i = 0; i < _encodedCodeword.length; i++) { + finalCodeword[i] = _encodedCodeword[i]; + } + + for (int i = 0; i < correctCodeword.length; i++) { + finalCodeword[i + _encodedCodeword.length] = correctCodeword[i]; + } + + return finalCodeword; + } + + /// Method used to prepare the code word + List _getDataCodeword(List dataCodeword) { + _encoding = _dataMatrixSymbology.encoding; + if (_dataMatrixSymbology.encoding == DataMatrixEncoding.auto || + _dataMatrixSymbology.encoding == DataMatrixEncoding.asciiNumeric) { + bool number = true; + bool extended = false; + int num = 0; + final List data = dataCodeword; + DataMatrixEncoding actualEncoding = DataMatrixEncoding.ascii; + for (int i = 0; i < data.length; i++) { + if ((data[i] < 48) || (data[i] > 57)) { + number = false; + } else if (data[i] > 127) { + num++; + if (num > 3) { + extended = true; + break; + } + } + } + + if (number) { + actualEncoding = DataMatrixEncoding.asciiNumeric; + } + + if (extended) { + actualEncoding = DataMatrixEncoding.base256; + } + + if (actualEncoding == DataMatrixEncoding.asciiNumeric && + _dataMatrixSymbology.encoding != actualEncoding) { + throw 'Data contains invalid characters and cannot be encoded as' + 'ASCIINumeric.'; + } + + _encoding = actualEncoding; + } + + return _getEncoding(dataCodeword); + } + + /// Method to get the encoding type + List _getEncoding(List dataCodeword) { + List result; + switch (_encoding) { + case DataMatrixEncoding.ascii: + result = _getDataMatrixASCIIEncoder(dataCodeword); + break; + case DataMatrixEncoding.asciiNumeric: + result = _getDataMatrixASCIINumericEncoder(dataCodeword); + break; + case DataMatrixEncoding.base256: + result = _getDataMatrixBaseEncoder(dataCodeword); + break; + case DataMatrixEncoding.auto: + break; + } + + return result; + } + + @override + bool getIsValidateInput(String value) { + return true; + } + + @override + void renderBarcode( + Canvas canvas, + Size size, + Offset offset, + String value, + Color foregroundColor, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + bool showValue) { + _inputValue = value; + + _buildDataMatrix(); + const int quietZone = 1; + double x = 0; + double y = 0; + final Paint paint = getBarPaint(foregroundColor); + final int w = _symbolAttribute.symbolRow + (2 * quietZone); + final int h = _symbolAttribute.symbolColumn + (2 * quietZone); + final double minSize = size.width >= size.height ? size.height : size.width; + final bool isSquareMatrix = + getDataMatrixSize(_dataMatrixSymbology.dataMatrixSize) < 25; + int dimension = minSize ~/ _dataMatrixList.length; + final int rectDimension = minSize ~/ _dataMatrixList[0].length; + final int xDimension = _dataMatrixSymbology.module ?? + (isSquareMatrix ? dimension : rectDimension); + dimension = _dataMatrixSymbology.module ?? dimension; + for (int i = 0; i < w; i++) { + x = 0; + for (int j = 0; j < h; j++) { + x += xDimension; + } + + y += dimension; + } + + final double xPosition = offset.dx + (size.width - x) / 2; + double yPosition = offset.dy + (size.height - y) / 2; + + for (int i = 0; i < w; i++) { + x = xPosition; + for (int j = 0; j < h; j++) { + if (_dataMatrixList[i][j] == 1) { + final Rect matrixRect = Rect.fromLTRB( + x, yPosition, x + xDimension, yPosition + dimension); + canvas.drawRect(matrixRect, paint); + } + + x += xDimension; + } + + yPosition += dimension; + } + + if (showValue) { + final Offset textOffset = Offset(offset.dx, yPosition.toDouble()); + drawText( + 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]) { + 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); + double x; + double y; + switch (textAlign) { + case TextAlign.justify: + case TextAlign.center: + { + x = offset.dx + (size.width / 2 - textPainter.width / 2); + y = offset.dy + textSpacing; + } + break; + case TextAlign.left: + case TextAlign.start: + { + x = offset.dx; + y = offset.dy + textSpacing; + } + break; + case TextAlign.right: + case TextAlign.end: + { + x = offset.dx + (size.width - textPainter.width); + y = offset.dy + textSpacing; + } + break; + } + textPainter.paint(canvas, Offset(x, y)); + } + + void _buildDataMatrix() { + final List codeword = _getCodeword(_getData()); + _createMatrix(codeword); + } +} + +/// 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}); + + /// Defines the symbol row + final int symbolRow; + + /// Defines the symbol column + final int symbolColumn; + + /// Defines the horizontal data region + final int horizontalDataRegion; + + /// Defines the vertical data region + final int verticalDataRegion; + + /// Defines the data code words + final int dataCodeWords; + + /// Defines the error correction code words + final int correctionCodeWords; + + /// Defines the inter leaved blocks + final int interLeavedBlock; + + /// Defines the inter leaved data blocks + final int interLeavedDataBlock; +} diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/error_correction_codewords.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/error_correction_codewords.dart similarity index 97% rename from packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/error_correction_codewords.dart rename to packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/error_correction_codewords.dart index 41d4334dc..d4c467b26 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/error_correction_codewords.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/error_correction_codewords.dart @@ -1,12 +1,13 @@ -part of barcodes; +import '../../utils/enum.dart'; +import 'qr_code_values.dart'; /// Represents the error correction code word -class _ErrorCorrectionCodeWords { +class ErrorCorrectionCodeWords { /// Creates the error correction code word - _ErrorCorrectionCodeWords({this.codeVersion, this.correctionLevel}) { - _codeValue = _QRCodeValue( + ErrorCorrectionCodeWords({this.codeVersion, this.correctionLevel}) { + _codeValue = QRCodeValue( qrCodeVersion: codeVersion, errorCorrectionLevel: correctionLevel); - _eccw = _codeValue._noOfErrorCorrectionCodeWord; + eccw = _codeValue.noOfErrorCorrectionCodeWord; } /// Specifies the code version @@ -275,13 +276,13 @@ class _ErrorCorrectionCodeWords { ]; /// Specifies the error corrcetion code word - int _eccw; + int eccw; /// Specifies the data bits - int _dataBits; + int dataBits; /// Specifies the data code word - List _dataCodeWords; + List dataCodeWords; /// Specifies the list of integer value based on alpha value List _gx; @@ -290,7 +291,7 @@ class _ErrorCorrectionCodeWords { List _decimalValue; /// Specifies the code value - _QRCodeValue _codeValue; + QRCodeValue _codeValue; /// Returns the error correction word /// @@ -298,11 +299,11 @@ class _ErrorCorrectionCodeWords { /// refactored to a smaller methods, but it degrades the performance.Since it /// uses single switch condition for calculating the error correction code /// word based on the provided version - List _getERCW() { + List getERCW() { List decimalRepresentation; List errorCorrectionWord; - _decimalValue = [_dataBits]; - switch (_eccw) { + _decimalValue = [dataBits]; + switch (eccw) { case 7: _gx = [0, 87, 229, 146, 149, 238, 102, 21]; break; @@ -1598,7 +1599,7 @@ class _ErrorCorrectionCodeWords { } _gx = _getElement(_gx, _alpha); - _binaryToDecimal(_dataCodeWords); + _binaryToDecimal(dataCodeWords); decimalRepresentation = _getPolynominalDivision(); errorCorrectionWord = _convertDecimalToBinary(decimalRepresentation); return errorCorrectionWord; @@ -1615,7 +1616,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 < eccw; i++) { final String temp = decimalRepresentation[i].toRadixString(2); String text = ''; @@ -1648,11 +1649,11 @@ class _ErrorCorrectionCodeWords { for (int i = 0; i < messagePolynom.length; i++) { final MapEntry currentEntry = messagePolynom.entries.elementAt(i); - tempMessagePolynom[currentEntry.key + _eccw] = currentEntry.value; + tempMessagePolynom[currentEntry.key + eccw] = currentEntry.value; } messagePolynom = tempMessagePolynom; - final int leadTermFactor = _decimalValue.length + _eccw - _gx.length; + final int leadTermFactor = _decimalValue.length + eccw - _gx.length; tempMessagePolynom = {}; for (int i = 0; i < generatorPolynom.length; i++) { @@ -1678,7 +1679,7 @@ class _ErrorCorrectionCodeWords { } } - _eccw = leadTermSource.length; + eccw = leadTermSource.length; final List returnValue = []; for (int i = 0; i < leadTermSource.length; i++) { returnValue.add(leadTermSource.entries.elementAt(i).value); 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 new file mode 100644 index 000000000..3b5a23779 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/qr_code_renderer.dart @@ -0,0 +1,1963 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../base/symbology_base.dart'; +import '../../two_dimensional/qr_code_symbology.dart'; +import '../../utils/enum.dart'; +import '../../utils/helper.dart'; +import '../one_dimensional/symbology_base_renderer.dart'; +import 'error_correction_codewords.dart'; +import 'qr_code_values.dart'; + +/// Represents the QRCode renderer class +class QRCodeRenderer extends SymbologyRenderer { + /// Creates the qr code renderer + QRCodeRenderer({Symbology symbology}) : super(symbology: symbology) { + _qrCodeSymbology = symbology; + if (_qrCodeSymbology.codeVersion != null && + _qrCodeSymbology.codeVersion != QRCodeVersion.auto) { + _codeVersion = _qrCodeSymbology.codeVersion; + _isUserMentionedVersion = true; + } else { + _isUserMentionedVersion = false; + } + + if (_qrCodeSymbology.errorCorrectionLevel != null) { + _errorCorrectionLevel = _qrCodeSymbology.errorCorrectionLevel; + _isUserMentionedErrorCorrectionLevel = true; + } else { + _isUserMentionedErrorCorrectionLevel = false; + } + + if (_qrCodeSymbology.inputMode != null) { + _inputMode = _qrCodeSymbology.inputMode; + _isUserMentionedMode = true; + } else { + _isUserMentionedMode = false; + } + } + + /// Specifies the qr code symbology + QRCode _qrCodeSymbology; + + /// Specifies the CP437 character set + static const List _cp437CharSet = [ + '2591', + '2592', + '2593', + '2502', + '2524', + '2561', + '2562', + '2556', + '2555', + '2563', + '2551', + '2557', + '255D', + '255C', + '255B', + '2510', + '2514', + '2534', + '252C', + '251C', + '2500', + '253C', + '255E', + '255F', + '255A', + '2554', + '2569', + '2566', + '2560', + '2550', + '256C', + '2567', + '2568', + '2564', + '2565', + '2559', + '2558', + '2552', + '2553', + '256B', + '256A', + '2518', + '250C', + '2588', + '2584', + '258C', + '2590', + '2580', + '25A0' + ]; + + /// Specifies the latin2 character set + static const List _latin2CharSet = [ + '104', + '2D8', + '141', + '13D', + '15A', + '160', + '15E', + '164', + '179', + '17D', + '17B', + '105', + '2DB', + '142', + '13E', + '15B', + '2C7', + '161', + '15F', + '165', + '17A', + '2DD', + '17E', + '17C', + '154', + '102', + '139', + '106', + '10C', + '118', + '11A', + '10E', + '110', + '143', + '147', + '150', + '158', + '16E', + '170', + '162', + '155', + '103', + '13A', + '107', + '10D', + '119', + '11B', + '10F', + '111', + '144', + '148', + '151', + '159', + '16F', + '171', + '163', + '2D9' + ]; + + /// Specifies the latin3 character set + static const List _latin3CharSet = [ + '126', + '124', + '130', + '15E', + '11E', + '134', + '17B', + '127', + '125', + '131', + '15F', + '11F', + '135', + '17C', + '10A', + '108', + '120', + '11C', + '16C', + '15C', + '10B', + '109', + '121', + '11D', + '16D', + '15D' + ]; + + /// Specifies the latin4 character set + static const List _latin4CharSet = [ + '104', + '138', + '156', + '128', + '13B', + '160', + '112', + '122', + '166', + '17D', + '105', + '2DB', + '157', + '129', + '13C', + '2C7', + '161', + '113', + '123', + '167', + '14A', + '17E', + '14B', + '100', + '12E', + '10C', + '118', + '116', + '12A', + '110', + '145', + '14C', + '136', + '172', + '168', + '16A', + '101', + '12F', + '10D', + '119', + '117', + '12B', + '111', + '146', + '14D', + '137', + '173', + '169', + '16B' + ]; + + /// Specifies the windows 1250 character set + static const List _windows1250CharSet = [ + '141', + '104', + '15E', + '17B', + '142', + '105', + '15F', + '13D', + '13E', + '17C' + ]; + + /// Specifies the windows 1251 character set + static const List _windows1251CharSet = [ + '402', + '403', + '453', + '409', + '40A', + '40C', + '40B', + '40F', + '452', + '459', + '45A', + '45C', + '45B', + '45F', + '40E', + '45E', + '408', + '490', + '401', + '404', + '407', + '406', + '456', + '491', + '451', + '454', + '458', + '405', + '455', + '457' + ]; + + /// Specifies the windows 1252 character set + static const List _windows1252CharSet = [ + '20AC', + '201A', + '192', + '201E', + '2026', + '2020', + '202', + '2C6', + '2030', + '160', + '2039', + '152', + '17D', + '2018', + '2019', + '201C', + '201D', + '2022', + '2013', + '2014', + '2DC', + '2122', + '161', + '203A', + '153', + '17E', + '178' + ]; + + /// Specifies the QR code value + QRCodeValue _qrCodeValues; + + /// Specifies the QR code version + QRCodeVersion _codeVersion = QRCodeVersion.version01; + + /// Species the module value + int _noOfModules = 21; + + /// Specifies the list of module value + List> _moduleValues; + + /// Specifies the data alllocation value + List> _dataAllocationValues; + + /// Specifies the actual input mode + QRInputMode _inputMode = QRInputMode.numeric; + + /// Specifies the actula error corrceton level + ErrorCorrectionLevel _errorCorrectionLevel = ErrorCorrectionLevel.low; + + /// Specifies the dat bits value + int _dataBits = 0; + + /// Specifies the list of blocks + List _blocks; + + /// Specifies the user mentioned level + bool _isUserMentionedMode = false; + + /// Specifies the version is mentioned by user + bool _isUserMentionedVersion = false; + + /// Specifies the error correction level is mentioned by user + bool _isUserMentionedErrorCorrectionLevel = false; + + /// Specifies the bool value based on the error correction input + bool _isEci = false; + + /// Specifies the assignment number + int _eciAssignmentNumber = 3; + + /// Specifies the text value + String _encodedText; + + /// Specifies the list of encode data + List _encodeDataCodeWords; + + @override + bool getIsValidateInput(String value) { + return true; + } + + /// Specifies the character set of CP437 + bool _getIsCP437Character(int inputChar) { + final String inputCharIndex = inputChar.toRadixString(16); + if (_cp437CharSet.contains(inputCharIndex)) { + return true; + } + + return false; + } + + /// Gets the input character is present in ISO8859_2 character set + bool _getIsISO8859_2CharacterSet(int input) { + final String inputInHex = input.toRadixString(16); + if (_latin2CharSet.contains(inputInHex)) { + return true; + } + return false; + } + + /// Checks the input character is present in ISO8859_3 character set + bool _getIsISO8859_3CharacterSet(int input) { + final String inputInHex = input.toRadixString(16); + + if (_latin3CharSet.contains(inputInHex)) { + return true; + } + return false; + } + + /// Checks the input character is present in ISO8859_4 character set + bool _getIsISO8859_4CharacterSet(int input) { + final String inputInHex = input.toRadixString(16); + if (_latin4CharSet.contains(inputInHex)) { + return true; + } + return false; + } + + /// Checks the input character is present in ISO8859_4 character set + bool _getIsISO8859_5CharacterSet(int input) { + if (input >= 1025 && + input <= 1119 && + input != 1037 && + input != 1104 && + input != 1117) { + return true; + } + + return false; + } + + ///Checks the input character is present in ISO8859_5 character set + bool _getIsISO8859_6CharacterSet(int input) { + if ((input >= 1569 && input <= 1594) || + (input >= 1600 && input <= 1618) || + input == 1567 || + input == 1563 || + input == 1548) { + return true; + } + + return false; + } + + ///Checks the input character is present in ISO8859_7 character set + bool _getIsISO8859_7CharacterSet(int input) { + if ((input >= 900 && input <= 974) || input == 890) { + return true; + } + + return false; + } + + /// Checks the input character is present in ISO8859_8 character set + bool _getIsISO8859_8CharacterSet(int input) { + if (input >= 1488 && input <= 1514) { + return true; + } + + return false; + } + + /// Checks the input character is present in ISO8859_811character set + bool _getIsISO8859_11CharacterSet(int input) { + if (input >= 3585 && input <= 3675) { + return true; + } + + return false; + } + + /// Checks the input character is present in Windows1250Character set + bool _getIsWindows1250Character(int input) { + final String inputCharHex = input.toRadixString(16); + if (_windows1250CharSet.contains(inputCharHex)) { + return true; + } + + if (input >= 1569 && input <= 1610) { + return true; + } + + return false; + } + + /// Checks the input character is present in Windows1251Character set + bool _getIsWindows1251Character(int input) { + final String inputChar = input.toRadixString(16); + if (_windows1251CharSet.contains(inputChar)) { + return true; + } + + if (input >= 1040 && input <= 1103) { + return true; + } + + return false; + } + + /// Checks the input character is present in Windows1252Character set + bool _getIsWindows1252Character(int input) { + final String inputChar = input.toRadixString(16); + + if (_windows1252CharSet.contains(inputChar)) { + return true; + } + + return false; + } + + /// Checks the input character is present in Windows1256Character set + bool _getIsWindows1256Character(int input) { + final String inputChar = input.toRadixString(16); + final List windows1256CharSet = [ + '67E', + '679', + '152', + '686', + '698', + '688', + '6AF', + '6A9', + '691', + '153', + '6BA', + '6BE', + '6C1' + ]; + + if (windows1256CharSet.contains(inputChar)) { + return true; + } + + if (input >= 1569 && input <= 1610) { + return true; + } + + return false; + } + + /// Allocates the format and the version information + void _allocateFormatAndVersionInformation() { + for (int i = 0; i < 9; i++) { + _moduleValues[8][i].isFilled = true; + _moduleValues[i][8].isFilled = true; + } + + for (int i = _noOfModules - 8; i < _noOfModules; i++) { + _moduleValues[8][i].isFilled = true; + _moduleValues[i][8].isFilled = true; + } + + final int version = getVersionNumber(_codeVersion); + if (version > 6) { + final List versionInformation = _qrCodeValues.versionInformation; + int count = 0; + for (int i = 0; i < 6; i++) { + for (int j = 2; j >= 0; j--) { + _moduleValues[i][_noOfModules - 9 - j].isBlack = + versionInformation != null && versionInformation[count] == 1 + ? true + : false; + _moduleValues[i][_noOfModules - 9 - j].isFilled = true; + + _moduleValues[_noOfModules - 9 - j][i].isBlack = + versionInformation != null && versionInformation[count++] == 1 + ? true + : false; + _moduleValues[_noOfModules - 9 - j][i].isFilled = true; + } + } + } + } + + /// Returns the alignment pattern + /// + /// This is a very large method. This method could not be + /// refactored to a smaller methods, since it has single switch condition and + /// returns the alignment pattern based on the QR Code version + List _getAlignmentPatternCoordinates() { + List align; + final int versionNumber = getVersionNumber(_codeVersion); + switch (versionNumber) { + case 02: + align = [6, 18]; + break; + case 03: + align = [6, 22]; + break; + case 04: + align = [6, 26]; + break; + case 05: + align = [6, 30]; + break; + case 06: + align = [6, 34]; + break; + case 07: + align = [6, 22, 38]; + break; + case 08: + align = [6, 24, 42]; + break; + case 09: + align = [6, 26, 46]; + break; + case 10: + align = [6, 28, 50]; + break; + case 11: + align = [6, 30, 54]; + break; + case 12: + align = [6, 32, 58]; + break; + case 13: + align = [6, 34, 62]; + break; + case 14: + align = [6, 26, 46, 66]; + break; + case 15: + align = [6, 26, 48, 70]; + break; + case 16: + align = [6, 26, 50, 74]; + break; + case 17: + align = [6, 30, 54, 78]; + break; + case 18: + align = [6, 30, 56, 82]; + break; + case 19: + align = [6, 30, 58, 86]; + break; + case 20: + align = [6, 34, 62, 90]; + break; + case 21: + align = [6, 28, 50, 72, 94]; + break; + case 22: + align = [6, 26, 50, 74, 98]; + break; + case 23: + align = [6, 30, 54, 78, 102]; + break; + case 24: + align = [6, 28, 54, 80, 106]; + break; + case 25: + align = [6, 32, 58, 84, 110]; + break; + case 26: + align = [6, 30, 58, 86, 114]; + break; + case 27: + align = [6, 34, 62, 90, 118]; + break; + case 28: + align = [6, 26, 50, 74, 98, 122]; + break; + case 29: + align = [6, 30, 54, 78, 102, 126]; + break; + case 30: + align = [6, 26, 52, 78, 104, 130]; + break; + case 31: + align = [6, 30, 56, 82, 108, 134]; + break; + case 32: + align = [6, 34, 60, 86, 112, 138]; + break; + case 33: + align = [6, 30, 58, 86, 114, 142]; + break; + case 34: + align = [6, 34, 62, 90, 118, 146]; + break; + case 35: + align = [6, 30, 54, 78, 102, 126, 150]; + break; + case 36: + align = [6, 24, 50, 76, 102, 128, 154]; + break; + case 37: + align = [6, 28, 54, 80, 106, 132, 158]; + break; + case 38: + align = [6, 32, 58, 84, 110, 136, 162]; + break; + case 39: + align = [6, 26, 54, 82, 110, 138, 166]; + break; + case 40: + align = [6, 30, 58, 86, 114, 142, 170]; + break; + } + return align; + } + + /// Converts the string to the list of bool + List _getStringToBoolArray(String numberInString, int noOfBits) { + final List numberInBool = List(noOfBits); + int j = 0; + for (int i = 0; i < numberInString.length; i++) { + j = j * 10 + numberInString[i].codeUnitAt(0) - 48; + } + + for (int i = 0; i < noOfBits; i++) { + numberInBool[noOfBits - i - 1] = ((j >> i) & 1) == 1; + } + return numberInBool; + } + + /// Converts the integer to bool array value + List _getIntToBoolArray(int number, int noOfBits) { + final List numberInBool = List(noOfBits); + for (int i = 0; i < noOfBits; i++) { + numberInBool[noOfBits - i - 1] = ((number >> i) & 1) == 1; + } + + return numberInBool; + } + + ///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(encodeData.length ~/ 8 ~/ noOfBlocks)); + + String stringValue = ''; + int j = 0; + int i = 0; + int blockNumber = 0; + for (; j < encodeData.length; j++) { + if (j % 8 == 0 && j != 0) { + encodedBlocks[blockNumber][i] = stringValue; + stringValue = ''; + i++; + + if (i == (encodeData.length / noOfBlocks / 8)) { + blockNumber++; + i = 0; + } + } + + stringValue += encodeData[j] ? '1' : '0'; + } + + encodedBlocks[blockNumber][i] = stringValue; + return encodedBlocks; + } + + /// Method to the split code word + List _splitCodeWord( + List> encodeData, int block, int count) { + final List encodeDataString = List(count); + for (int i = 0; i < count; i++) { + encodeDataString[i] = encodeData[block][i]; + } + + return encodeDataString; + } + + /// Method to find the actual mode, correction level and the input level + void _initialize() { + QRInputMode actualMode = QRInputMode.numeric; + for (int i = 0; i < _encodedText.length; i++) { + if (_encodedText[i].codeUnitAt(0) < 58 && + _encodedText[i].codeUnitAt(0) > 47) { + } else if ((_encodedText[i].codeUnitAt(0) < 91 && + _encodedText[i].codeUnitAt(0) > 64) || + _encodedText[i] == '\$' || + _encodedText[i] == '%' || + _encodedText[i] == '*' || + _encodedText[i] == '+' || + _encodedText[i] == '-' || + _encodedText[i] == '.' || + _encodedText[i] == '/' || + _encodedText[i] == ':' || + _encodedText[i] == ' ') { + actualMode = QRInputMode.alphaNumeric; + } else if ((_encodedText[i].codeUnitAt(0) >= 65377 && + _encodedText[i].codeUnitAt(0) <= 65439) || + (_encodedText[i].codeUnitAt(0) >= 97 && + _encodedText[i].codeUnitAt(0) <= 122)) { + actualMode = QRInputMode.binary; + + break; + } else { + actualMode = QRInputMode.binary; + _isEci = true; + break; + } + } + + if (_isUserMentionedMode) { + if (actualMode != _qrCodeSymbology.inputMode) { + if (((actualMode == QRInputMode.alphaNumeric || + actualMode == QRInputMode.binary) && + _qrCodeSymbology.inputMode == QRInputMode.numeric) || + (actualMode == QRInputMode.binary && + _qrCodeSymbology.inputMode == QRInputMode.alphaNumeric)) { + // new BarcodeException("Mode Conflict"); + } + } + } + + _inputMode = actualMode; + _initializeECI(); + _initializeVersionAndECR(); + _noOfModules = (getVersionNumber(_codeVersion) - 1) * 4 + 21; + } + + /// Method to initialize the error correction level + /// + /// This is a very large method. This method could not be + /// refactored to a smaller methods, since it has single for loop and + /// returns the ECI based on the encoded text + void _initializeECI() { + if (_isEci) { + for (int i = 0; i < _encodedText.length; i++) { + if (_encodedText[i].codeUnitAt(0) >= 32 && + _encodedText[i].codeUnitAt(0) <= 255) { + continue; + } + //Check for CP437 + if (_getIsCP437Character(_encodedText[i].codeUnitAt(0))) { + _eciAssignmentNumber = 2; + break; + } + + //Check for ISO/IEC 8859-2 + else if (_getIsISO8859_2CharacterSet(_encodedText[i].codeUnitAt(0))) { + _eciAssignmentNumber = 4; + break; + } + //Check for ISO/IEC 8859-3 + else if (_getIsISO8859_3CharacterSet(_encodedText[i].codeUnitAt(0))) { + _eciAssignmentNumber = 5; + break; + } + //Check for ISO/IEC 8859-4 + else if (_getIsISO8859_4CharacterSet(_encodedText[i].codeUnitAt(0))) { + _eciAssignmentNumber = 6; + break; + } + //Check for ISO/IEC 8859-5 + else if (_getIsISO8859_5CharacterSet(_encodedText[i].codeUnitAt(0))) { + _eciAssignmentNumber = 7; + break; + } + //Check for ISO/IEC 8859-6 + else if (_getIsISO8859_6CharacterSet(_encodedText[i].codeUnitAt(0))) { + _eciAssignmentNumber = 8; + break; + } + //Check for ISO/IEC 8859-7 + else if (_getIsISO8859_7CharacterSet(_encodedText[i].codeUnitAt(0))) { + _eciAssignmentNumber = 9; + break; + } + //Check for ISO/IEC 8859-8 + else if (_getIsISO8859_8CharacterSet(_encodedText[i].codeUnitAt(0))) { + _eciAssignmentNumber = 10; + break; + } + //Check for ISO/IEC 8859-8 + else if (_getIsISO8859_11CharacterSet(_encodedText[i].codeUnitAt(0))) { + _eciAssignmentNumber = 13; + break; + } + + //Check for Windows1250 + else if (_getIsWindows1250Character(_encodedText[i].codeUnitAt(0))) { + _eciAssignmentNumber = 21; + break; + } + //Check for Windows1251 + else if (_getIsWindows1251Character(_encodedText[i].codeUnitAt(0))) { + _eciAssignmentNumber = 22; + break; + } + //Check for Windows1252 + else if (_getIsWindows1252Character(_encodedText[i].codeUnitAt(0))) { + _eciAssignmentNumber = 23; + break; + } + //Check for Windows1256 + else if (_getIsWindows1256Character(_encodedText[i].codeUnitAt(0))) { + _eciAssignmentNumber = 24; + break; + } + } + } + } + + /// Method to initialize the version and the error correction level + /// + /// This is a very large method. This method could not be + /// refactored to a smaller methods, since it has single if else condition and + /// it sets the data capacity and error correction level based on the input + /// mode + void _initializeVersionAndECR() { + if (!_isUserMentionedVersion || _codeVersion == QRCodeVersion.auto) { + List dataCapacityOfVersions; + if (_isUserMentionedErrorCorrectionLevel) { + switch (_inputMode) { + case QRInputMode.numeric: + { + dataCapacityOfVersions = _getDataCapacityForNumericMode(); + break; + } + + case QRInputMode.alphaNumeric: + { + dataCapacityOfVersions = _getDataCapacityForAlphaNumericMode(); + break; + } + + case QRInputMode.binary: + { + dataCapacityOfVersions = _getDataCapacityForBinaryMode(); + break; + } + } + } else { + _errorCorrectionLevel = ErrorCorrectionLevel.low; + dataCapacityOfVersions = _getDataCapacityVersionForInputMode(); + } + int i; + for (i = 0; i < dataCapacityOfVersions.length; i++) { + if (dataCapacityOfVersions[i] > _encodedText.length) { + break; + } + } + + final int calculatedVersion = i + 1; + + if (calculatedVersion > 40) { + return; + } else { + _codeVersion = getVersionBasedOnNumber(calculatedVersion); + } + } else if (_isUserMentionedVersion) { + if (_isUserMentionedErrorCorrectionLevel) { + int capacity = 0; + if (_inputMode == QRInputMode.alphaNumeric) { + capacity = QRCodeValue.getAlphaNumericDataCapacity( + _errorCorrectionLevel, _codeVersion); + } else if (_inputMode == QRInputMode.numeric) { + capacity = QRCodeValue.getNumericDataCapacity( + _errorCorrectionLevel, _codeVersion); + } else if (_inputMode == QRInputMode.binary) { + capacity = QRCodeValue.getBinaryDataCapacity( + _errorCorrectionLevel, _codeVersion); + } + if (capacity < _encodedText.length) { + throw 'The input value length is greater than version capacity'; + } + } else { + int capacityLow = 0, + capacityMedium = 0, + capacityQuartile = 0, + capacityHigh = 0; + if (_inputMode == QRInputMode.alphaNumeric) { + capacityLow = QRCodeValue.getAlphaNumericDataCapacity( + ErrorCorrectionLevel.low, _codeVersion); + capacityMedium = QRCodeValue.getAlphaNumericDataCapacity( + ErrorCorrectionLevel.medium, _codeVersion); + capacityQuartile = QRCodeValue.getAlphaNumericDataCapacity( + ErrorCorrectionLevel.quartile, _codeVersion); + capacityHigh = QRCodeValue.getAlphaNumericDataCapacity( + ErrorCorrectionLevel.high, _codeVersion); + } else if (_inputMode == QRInputMode.numeric) { + capacityLow = QRCodeValue.getNumericDataCapacity( + ErrorCorrectionLevel.low, _codeVersion); + capacityMedium = QRCodeValue.getNumericDataCapacity( + ErrorCorrectionLevel.medium, _codeVersion); + capacityQuartile = QRCodeValue.getNumericDataCapacity( + ErrorCorrectionLevel.quartile, _codeVersion); + capacityHigh = QRCodeValue.getNumericDataCapacity( + ErrorCorrectionLevel.high, _codeVersion); + } else if (_inputMode == QRInputMode.binary) { + capacityLow = QRCodeValue.getBinaryDataCapacity( + ErrorCorrectionLevel.low, _codeVersion); + capacityMedium = QRCodeValue.getBinaryDataCapacity( + ErrorCorrectionLevel.medium, _codeVersion); + capacityQuartile = QRCodeValue.getBinaryDataCapacity( + ErrorCorrectionLevel.quartile, _codeVersion); + capacityHigh = QRCodeValue.getBinaryDataCapacity( + ErrorCorrectionLevel.high, _codeVersion); + } + + if (capacityHigh > _encodedText.length) { + _errorCorrectionLevel = ErrorCorrectionLevel.high; + } else if (capacityQuartile > _encodedText.length) { + _errorCorrectionLevel = ErrorCorrectionLevel.quartile; + } else if (capacityMedium > _encodedText.length) { + _errorCorrectionLevel = ErrorCorrectionLevel.medium; + } else if (capacityLow > _encodedText.length) { + _errorCorrectionLevel = ErrorCorrectionLevel.low; + } else { + throw 'The input value length is greater than version capacity'; + } + } + } + } + + /// Returns the data capacity for numeric mode + List _getDataCapacityForNumericMode() { + List dataCapacityOfVersions; + switch (_errorCorrectionLevel) { + case ErrorCorrectionLevel.low: + dataCapacityOfVersions = QRCodeValue.numericDataCapacityLow; + break; + case ErrorCorrectionLevel.medium: + dataCapacityOfVersions = QRCodeValue.numericDataCapacityMedium; + break; + case ErrorCorrectionLevel.quartile: + dataCapacityOfVersions = QRCodeValue.numericDataCapacityQuartile; + break; + case ErrorCorrectionLevel.high: + dataCapacityOfVersions = QRCodeValue.numericDataCapacityHigh; + break; + } + + return dataCapacityOfVersions; + } + + /// Returns the data capacity for alpha numeric mode + List _getDataCapacityForAlphaNumericMode() { + List dataCapacityOfVersions; + switch (_errorCorrectionLevel) { + case ErrorCorrectionLevel.low: + dataCapacityOfVersions = QRCodeValue.alphaNumericDataCapacityLow; + break; + case ErrorCorrectionLevel.medium: + dataCapacityOfVersions = QRCodeValue.alphaNumericDataCapacityMedium; + break; + case ErrorCorrectionLevel.quartile: + dataCapacityOfVersions = QRCodeValue.alphaNumericDataCapacityQuartile; + break; + case ErrorCorrectionLevel.high: + dataCapacityOfVersions = QRCodeValue.alphaNumericDataCapacityHigh; + break; + } + + return dataCapacityOfVersions; + } + + /// Returns the data capacity for binary mode + List _getDataCapacityForBinaryMode() { + List dataCapacityOfVersions; + switch (_errorCorrectionLevel) { + case ErrorCorrectionLevel.low: + dataCapacityOfVersions = QRCodeValue.binaryDataCapacityLow; + break; + case ErrorCorrectionLevel.medium: + dataCapacityOfVersions = QRCodeValue.binaryDataCapacityMedium; + break; + case ErrorCorrectionLevel.quartile: + dataCapacityOfVersions = QRCodeValue.binaryDataCapacityQuartile; + break; + case ErrorCorrectionLevel.high: + dataCapacityOfVersions = QRCodeValue.binaryDataCapacityHigh; + break; + } + + return dataCapacityOfVersions; + } + + /// Returns the data capacity version for input + List _getDataCapacityVersionForInputMode() { + List dataCapacityOfVersions; + switch (_inputMode) { + case QRInputMode.numeric: + { + dataCapacityOfVersions = QRCodeValue.numericDataCapacityLow; + break; + } + + case QRInputMode.alphaNumeric: + { + dataCapacityOfVersions = QRCodeValue.alphaNumericDataCapacityLow; + break; + } + + case QRInputMode.binary: + { + dataCapacityOfVersions = QRCodeValue.binaryDataCapacityLow; + break; + } + } + + return dataCapacityOfVersions; + } + + /// Method to draw the format information + void _drawFormatInformation() { + final List formatInformation = _qrCodeValues.formatInformation; + int count = 0; + for (int i = 0; i < 7; i++) { + if (i == 6) { + _moduleValues[i + 1][8].isBlack = + formatInformation[count] == 1 ? true : false; + } else { + _moduleValues[i][8].isBlack = + formatInformation[count] == 1 ? true : false; + } + + _moduleValues[8][_noOfModules - i - 1].isBlack = + formatInformation[count++] == 1 ? true : false; + } + + count = 14; + + for (int i = 0; i < 7; i++) { + //Draw format information from 0 to 6 + if (i == 6) { + _moduleValues[8][i + 1].isBlack = + formatInformation[count] == 1 ? true : false; + } else { + _moduleValues[8][i].isBlack = + formatInformation[count] == 1 ? true : false; + } + + _moduleValues[_noOfModules - i - 1][8].isBlack = + formatInformation[count--] == 1 ? true : false; + } + + //Draw format information 7 + _moduleValues[8][8].isBlack = formatInformation[7] == 1 ? true : false; + _moduleValues[8][_noOfModules - 8].isBlack = + formatInformation[7] == 1 ? true : false; + } + + /// Method used for data allocation and masking + /// + /// This is deliberately a very large method. This method could be + /// refactored to a smaller methods, but it degrades the performance.Since it + /// 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())); + + int point = 0; + + for (int i = _noOfModules - 1; i >= 0; i -= 2) { + for (int j = _noOfModules - 1; j >= 0; j--) { + if (!(_moduleValues[i][j].isFilled && + _moduleValues[i - 1][j].isFilled)) { + if (!_moduleValues[i][j].isFilled) { + if (point + 1 < data.length) { + _dataAllocationValues[i][j].isBlack = data[point++]; + } + + if ((i + j) % 3 == 0) { + if (_dataAllocationValues[i][j].isBlack) { + _dataAllocationValues[i][j].isBlack = true; + } else { + _dataAllocationValues[i][j].isBlack = false; + } + } else { + if (_dataAllocationValues[i][j].isBlack) { + _dataAllocationValues[i][j].isBlack = false; + } else { + _dataAllocationValues[i][j].isBlack = true; + } + } + + _dataAllocationValues[i][j].isFilled = true; + } + + if (!_moduleValues[i - 1][j].isFilled) { + if (point + 1 < data.length) { + _dataAllocationValues[i - 1][j].isBlack = data[point++]; + } + + if ((i - 1 + j) % 3 == 0) { + if (_dataAllocationValues[i - 1][j].isBlack) { + _dataAllocationValues[i - 1][j].isBlack = true; + } else { + _dataAllocationValues[i - 1][j].isBlack = false; + } + } else { + if (_dataAllocationValues[i - 1][j].isBlack) { + _dataAllocationValues[i - 1][j].isBlack = false; + } else { + _dataAllocationValues[i - 1][j].isBlack = true; + } + } + _dataAllocationValues[i - 1][j].isFilled = true; + } + } + } + + i -= 2; + if (i == 6) { + i--; + } + + for (int j = 0; j < _noOfModules; j++) { + if (!(_moduleValues[i][j].isFilled && + _moduleValues[i - 1][j].isFilled)) { + if (!_moduleValues[i][j].isFilled) { + if (point + 1 < data.length) { + _dataAllocationValues[i][j].isBlack = data[point++]; + } + + if ((i + j) % 3 == 0) { + if (_dataAllocationValues[i][j].isBlack) { + _dataAllocationValues[i][j].isBlack = true; + } else { + _dataAllocationValues[i][j].isBlack = false; + } + } else { + if (_dataAllocationValues[i][j].isBlack) { + _dataAllocationValues[i][j].isBlack = false; + } else { + _dataAllocationValues[i][j].isBlack = true; + } + } + _dataAllocationValues[i][j].isFilled = true; + } + + if (!_moduleValues[i - 1][j].isFilled) { + if (point + 1 < data.length) { + _dataAllocationValues[i - 1][j].isBlack = data[point++]; + } + + if ((i - 1 + j) % 3 == 0) { + if (_dataAllocationValues[i - 1][j].isBlack) { + _dataAllocationValues[i - 1][j].isBlack = true; + } else { + _dataAllocationValues[i - 1][j].isBlack = false; + } + } else { + if (_dataAllocationValues[i - 1][j].isBlack) { + _dataAllocationValues[i - 1][j].isBlack = false; + } else { + _dataAllocationValues[i - 1][j].isBlack = true; + } + } + _dataAllocationValues[i - 1][j].isFilled = true; + } + } + } + } + for (int i = 0; i < _noOfModules; i++) { + for (int j = 0; j < _noOfModules; j++) { + if (!_moduleValues[i][j].isFilled) { + final bool flag = _dataAllocationValues[i][j].isBlack; + if (flag) { + _dataAllocationValues[i][j].isBlack = false; + } else { + _dataAllocationValues[i][j].isBlack = true; + } + } + } + } + } + + /// Method to draw the alignment pattern + void _drawAlignmentPattern(int x, int y) { + int i = x - 2, j = y - 2; + for (; i < x + 3; i++, j++) { + _moduleValues[i][y - 2].isBlack = true; + _moduleValues[i][y - 2].isFilled = true; + + _moduleValues[i][y + 2].isBlack = true; + _moduleValues[i][y + 2].isFilled = true; + + _moduleValues[x - 2][j].isBlack = true; + _moduleValues[x - 2][j].isFilled = true; + + _moduleValues[x + 2][j].isBlack = true; + _moduleValues[x + 2][j].isFilled = true; + } + + i = x - 1; + j = y - 1; + for (; i < x + 2; i++, j++) { + _moduleValues[i][y - 1].isBlack = false; + _moduleValues[i][y - 1].isFilled = true; + + _moduleValues[i][y + 1].isBlack = false; + _moduleValues[i][y + 1].isFilled = true; + + _moduleValues[x - 1][j].isBlack = false; + _moduleValues[x - 1][j].isFilled = true; + + _moduleValues[x + 1][j].isBlack = false; + _moduleValues[x + 1][j].isFilled = true; + } + + _moduleValues[x][y].isBlack = true; + _moduleValues[x][y].isFilled = true; + } + + /// Method to draw the timing pattern + void _drawTimingPattern() { + for (int i = 8; i < _noOfModules - 8; i += 2) { + _moduleValues[i][6].isBlack = true; + _moduleValues[i][6].isFilled = true; + + _moduleValues[i + 1][6].isBlack = false; + _moduleValues[i + 1][6].isFilled = true; + + _moduleValues[6][i].isBlack = true; + _moduleValues[6][i].isFilled = true; + + _moduleValues[6][i + 1].isBlack = false; + _moduleValues[6][i + 1].isFilled = true; + } + + _moduleValues[_noOfModules - 8][8].isBlack = true; + _moduleValues[_noOfModules - 8][8].isFilled = true; + } + + /// Method to draw the PDP + /// + /// This is a very large method. This method could be + /// refactored to a smaller methods, but it degrades the performance.Since it + /// uses multiple for loop for allocating the module values values + void _drawPDP(int x, int y) { + int i = x; + int j = y; + for (; i < x + 7; i++, j++) { + _moduleValues[i][y].isBlack = true; + _moduleValues[i][y].isFilled = true; + _moduleValues[i][y].isPDP = true; + + _moduleValues[i][y + 6].isBlack = true; + _moduleValues[i][y + 6].isFilled = true; + _moduleValues[i][y + 6].isPDP = true; + + if (y + 7 < _noOfModules) { + _moduleValues[i][y + 7].isBlack = false; + _moduleValues[i][y + 7].isFilled = true; + _moduleValues[i][y + 7].isPDP = true; + } else if (y - 1 >= 0) { + _moduleValues[i][y - 1].isBlack = false; + _moduleValues[i][y - 1].isFilled = true; + _moduleValues[i][y - 1].isPDP = true; + } + + _moduleValues[x][j].isBlack = true; + _moduleValues[x][j].isFilled = true; + _moduleValues[x][j].isPDP = true; + + _moduleValues[x + 6][j].isBlack = true; + _moduleValues[x + 6][j].isFilled = true; + _moduleValues[x + 6][j].isPDP = true; + + if (x + 7 < _noOfModules) { + _moduleValues[x + 7][j].isBlack = false; + _moduleValues[x + 7][j].isFilled = true; + _moduleValues[x + 7][j].isPDP = true; + } else if (x - 1 >= 0) { + _moduleValues[x - 1][j].isBlack = false; + _moduleValues[x - 1][j].isFilled = true; + _moduleValues[x - 1][j].isPDP = true; + } + } + + if (x + 7 < _noOfModules && y + 7 < _noOfModules) { + _moduleValues[x + 7][y + 7].isBlack = false; + _moduleValues[x + 7][y + 7].isFilled = true; + _moduleValues[x + 7][y + 7].isPDP = true; + } else if (x + 7 < _noOfModules && y + 7 >= _noOfModules) { + _moduleValues[x + 7][y - 1].isBlack = false; + _moduleValues[x + 7][y - 1].isFilled = true; + _moduleValues[x + 7][y - 1].isPDP = true; + } else if (x + 7 >= _noOfModules && y + 7 < _noOfModules) { + _moduleValues[x - 1][y + 7].isBlack = false; + _moduleValues[x - 1][y + 7].isFilled = true; + _moduleValues[x - 1][y + 7].isPDP = true; + } + + x++; + y++; + i = x; + j = y; + for (; i < x + 5; i++, j++) { + _moduleValues[i][y].isBlack = false; + _moduleValues[i][y].isFilled = true; + _moduleValues[i][y].isPDP = true; + + _moduleValues[i][y + 4].isBlack = false; + _moduleValues[i][y + 4].isFilled = true; + _moduleValues[i][y + 4].isPDP = true; + + _moduleValues[x][j].isBlack = false; + _moduleValues[x][j].isFilled = true; + _moduleValues[x][j].isPDP = true; + + _moduleValues[x + 4][j].isBlack = false; + _moduleValues[x + 4][j].isFilled = true; + _moduleValues[x + 4][j].isPDP = true; + } + + x++; + y++; + i = x; + j = y; + for (; i < x + 3; i++, j++) { + _moduleValues[i][y].isBlack = true; + _moduleValues[i][y].isFilled = true; + _moduleValues[i][y].isPDP = true; + + _moduleValues[i][y + 2].isBlack = true; + _moduleValues[i][y + 2].isFilled = true; + _moduleValues[i][y + 2].isPDP = true; + + _moduleValues[x][j].isBlack = true; + _moduleValues[x][j].isFilled = true; + _moduleValues[x][j].isPDP = true; + + _moduleValues[x + 2][j].isBlack = true; + _moduleValues[x + 2][j].isFilled = true; + _moduleValues[x + 2][j].isPDP = true; + } + + _moduleValues[x + 1][y + 1].isBlack = true; + _moduleValues[x + 1][y + 1].isFilled = true; + _moduleValues[x + 1][y + 1].isPDP = true; + } + + /// Method used to generate the value + void _generateValues() { + _initialize(); + _qrCodeValues = QRCodeValue( + qrCodeVersion: _codeVersion, + errorCorrectionLevel: _errorCorrectionLevel); + _moduleValues = List>.generate( + _noOfModules, + (int i) => + List.generate(_noOfModules, (int j) => ModuleValue())); + _drawPDP(0, 0); + _drawPDP(_noOfModules - 7, 0); + _drawPDP(0, _noOfModules - 7); + _drawTimingPattern(); + if (_codeVersion != QRCodeVersion.version01) { + final List alignCoordinates = _getAlignmentPatternCoordinates(); + + for (int i = 0; i < alignCoordinates.length; i++) { + for (int j = 0; j < alignCoordinates.length; j++) { + if (!_moduleValues[alignCoordinates[i]][alignCoordinates[j]].isPDP) { + _drawAlignmentPattern(alignCoordinates[i], alignCoordinates[j]); + } + } + } + } + + _allocateFormatAndVersionInformation(); + _encodeDataCodeWords = _encodeData(); + _dataAllocationAndMasking(_encodeDataCodeWords); + _drawFormatInformation(); + } + + /// Method to encoded the data based on mode + void _encodeDataBasedOnMode() { + switch (_inputMode) { + case QRInputMode.numeric: + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(true); + break; + + case QRInputMode.alphaNumeric: + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(true); + _encodeDataCodeWords.add(false); + break; + + case QRInputMode.binary: + if (_isEci) { + //Add ECI Mode Indicator + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(true); + _encodeDataCodeWords.add(true); + _encodeDataCodeWords.add(true); + + //Add ECI assignment number + final List numberInBool = + _getStringToBoolArray(_eciAssignmentNumber.toString(), 8); + for (int i = 0; i < numberInBool.length; i++) { + _encodeDataCodeWords.add(numberInBool[i]); + } + } + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(true); + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(false); + break; + } + } + + /// Method to get the indicator count for lower version + int _getIndicatorCountForLowerVersion() { + int numberOfBitsInCharacterCountIndicator = 0; + switch (_inputMode) { + case QRInputMode.numeric: + numberOfBitsInCharacterCountIndicator = 10; + break; + + case QRInputMode.alphaNumeric: + numberOfBitsInCharacterCountIndicator = 9; + break; + case QRInputMode.binary: + numberOfBitsInCharacterCountIndicator = 8; + break; + } + + return numberOfBitsInCharacterCountIndicator; + } + + /// method to get the indicator count for higher version + int _getIndicatorCountForHigherVersion() { + int numberOfBitsInCharacterCountIndicator = 0; + switch (_inputMode) { + case QRInputMode.numeric: + numberOfBitsInCharacterCountIndicator = 12; + break; + + case QRInputMode.alphaNumeric: + numberOfBitsInCharacterCountIndicator = 11; + break; + + case QRInputMode.binary: + numberOfBitsInCharacterCountIndicator = 16; + break; + } + + return numberOfBitsInCharacterCountIndicator; + } + + /// Method to get the indicator count + int _getIndicatorCount() { + int numberOfBitsInCharacterCountIndicator = 0; + switch (_inputMode) { + case QRInputMode.numeric: + numberOfBitsInCharacterCountIndicator = 14; + break; + + case QRInputMode.alphaNumeric: + numberOfBitsInCharacterCountIndicator = 13; + break; + + case QRInputMode.binary: + numberOfBitsInCharacterCountIndicator = 16; + break; + } + return numberOfBitsInCharacterCountIndicator; + } + + /// Method to encode the data of numeric mode + void _encodeDataInNumericMode() { + String number = ''; + for (int i = 0; i < _encodedText.length; i++) { + List numberInBool; + number += _encodedText[i]; + + if (i % 3 == 2 && i != 0 || i == _encodedText.length - 1) { + if (number.length == 3) { + numberInBool = _getStringToBoolArray(number, 10); + } else if (number.length == 2) { + numberInBool = _getStringToBoolArray(number, 7); + } else { + numberInBool = _getStringToBoolArray(number, 4); + } + + number = ''; + for (int i = 0; i < numberInBool.length; i++) { + _encodeDataCodeWords.add(numberInBool[i]); + } + } + } + } + + /// Method to encode the alpha numeric data + void _encodeDataForAlphaNumeric() { + String numberInString = ''; + int number = 0; + for (int i = 0; i < _encodedText.length; i++) { + List numberInBool; + numberInString += _encodedText[i]; + + if (i % 2 == 0 && i + 1 != _encodedText.length) { + number = 45 * _qrCodeValues.getAlphaNumericValues(_encodedText[i]); + } + + if (i % 2 == 1 && i != 0) { + number += _qrCodeValues.getAlphaNumericValues(_encodedText[i]); + numberInBool = _getIntToBoolArray(number, 11); + number = 0; + for (int i = 0; i < numberInBool.length; i++) { + _encodeDataCodeWords.add(numberInBool[i]); + } + + numberInString = ''; + } + if (i != 1 && numberInString != null) { + if (i + 1 == _encodedText.length && numberInString.length == 1) { + number = _qrCodeValues.getAlphaNumericValues(_encodedText[i]); + numberInBool = _getIntToBoolArray(number, 6); + number = 0; + for (int i = 0; i < numberInBool.length; i++) { + _encodeDataCodeWords.add(numberInBool[i]); + } + } + } + } + } + + /// method to encode the binary data + void _encodeDataForBinary() { + for (int i = 0; i < _encodedText.length; i++) { + int number = 0; + if ((_encodedText[i].codeUnitAt(0) >= 32 && + _encodedText[i].codeUnitAt(0) <= 126) || + _encodedText[i].codeUnitAt(0) >= 161 && + _encodedText[i].codeUnitAt(0) <= 255 || + _encodedText[i].codeUnitAt(0) == 10 || + _encodedText[i].codeUnitAt(0) == 13) { + number = _encodedText[i].codeUnitAt(0); + } else if (_encodedText[i].codeUnitAt(0) >= 65377 && + _encodedText[i].codeUnitAt(0) <= 65439) { + number = _encodedText[i].codeUnitAt(0) - 65216; + } else if (_encodedText[i].codeUnitAt(0) >= 1025 && + _encodedText[i].codeUnitAt(0) <= 1119) { + number = _encodedText[i].codeUnitAt(0) - 864; + } else if (_encodedText[i].codeUnitAt(0) >= 260 && + _encodedText[i].codeUnitAt(0) <= 382) { + /// European Encoding + } else { + throw 'The provided input value contains non-convertible characters'; + } + + final List numberInBool = _getIntToBoolArray(number, 8); + for (int i = 0; i < numberInBool.length; i++) { + _encodeDataCodeWords.add(numberInBool[i]); + } + } + } + + /// Method to encode the code words + /// + /// This is a very large method. This method could be + /// refactored to a smaller methods, but it degrades the performance.Since it + /// uses multiple for loop for encoding the code words + void _encodeCodeWords() { + for (int i = 0; i < 4; i++) { + if (_encodeDataCodeWords.length / 8 == _qrCodeValues.noOfDataCodeWord) { + break; + } else { + _encodeDataCodeWords.add(false); + } + } + + for (;;) { + if (_encodeDataCodeWords.length % 8 == 0) { + break; + } else { + _encodeDataCodeWords.add(false); + } + } + + for (;;) { + if (_encodeDataCodeWords.length / 8 == _qrCodeValues.noOfDataCodeWord) { + break; + } else { + _encodeDataCodeWords.add(true); + _encodeDataCodeWords.add(true); + _encodeDataCodeWords.add(true); + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(true); + _encodeDataCodeWords.add(true); + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(false); + } + if (_encodeDataCodeWords.length / 8 == _qrCodeValues.noOfDataCodeWord) { + break; + } else { + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(true); + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(false); + _encodeDataCodeWords.add(true); + } + } + + _dataBits = _qrCodeValues.noOfDataCodeWord; + _blocks = _qrCodeValues.noOfErrorCorrectionBlocks; + + int totalBlockSize = _blocks[0]; + if (_blocks.length == 6) { + totalBlockSize = _blocks[0] + _blocks[3]; + } + final List> ds1 = + List>.generate(totalBlockSize, (int i) => []); + + List testEncodeData = _encodeDataCodeWords; + if (_blocks.length == 6) { + final int dataCodeWordLength = _blocks[0] * _blocks[2] * 8; + testEncodeData = []; + for (int i = 0; i < dataCodeWordLength; i++) { + testEncodeData.add(_encodeDataCodeWords[i]); + } + } + + List> dsOne = List>.generate(_blocks[0], + (int i) => List(testEncodeData.length ~/ 8 ~/ _blocks[0])); + dsOne = _getBlocks(testEncodeData, _blocks[0]); + + for (int i = 0; i < _blocks[0]; i++) { + 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++) { + testEncodeData.add(_encodeDataCodeWords[i]); + } + + List> dsTwo = List>.generate(_blocks[0], + (int i) => List(testEncodeData.length ~/ 8 ~/ _blocks[3])); + 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]); + } + } + + _encodeDataCodeWords = []; + for (int i = 0; i < 125; i++) { + for (int k = 0; k < totalBlockSize; k++) { + for (int j = 0; j < 8; j++) { + if (i < ds1[k].length) { + _encodeDataCodeWords.add(ds1[k][i][j] == '1' ? true : false); + } + } + } + } + + _calculateErrorCorrectingCodeWord(totalBlockSize, ds1); + } + + /// Method to encode the data + List _encodeData() { + _encodeDataCodeWords = []; + _encodeDataBasedOnMode(); + + int numberOfBitsInCharacterCountIndicator = 0; + final int currentVersion = getVersionNumber(_codeVersion); + if (currentVersion < 10) { + numberOfBitsInCharacterCountIndicator = + _getIndicatorCountForLowerVersion(); + } else if (currentVersion < 27) { + numberOfBitsInCharacterCountIndicator = + _getIndicatorCountForHigherVersion(); + } else { + numberOfBitsInCharacterCountIndicator = _getIndicatorCount(); + } + + final List numberOfBitsInCharacterCountIndicatorInBool = + _getIntToBoolArray( + _encodedText.length, numberOfBitsInCharacterCountIndicator); + + for (int i = 0; i < numberOfBitsInCharacterCountIndicator; i++) { + _encodeDataCodeWords.add(numberOfBitsInCharacterCountIndicatorInBool[i]); + } + + switch (_inputMode) { + case QRInputMode.numeric: + _encodeDataInNumericMode(); + break; + case QRInputMode.alphaNumeric: + _encodeDataForAlphaNumeric(); + break; + case QRInputMode.binary: + _encodeDataForBinary(); + break; + } + + _encodeCodeWords(); + return _encodeDataCodeWords; + } + + /// Method to calculate the error correcting code word + void _calculateErrorCorrectingCodeWord( + int totalBlockSize, List> ds1) { + final ErrorCorrectionCodeWords errorCorrectionCodeWord = + ErrorCorrectionCodeWords( + codeVersion: _codeVersion, correctionLevel: _errorCorrectionLevel); + + _dataBits = _qrCodeValues.noOfDataCodeWord; + final int eccw = _qrCodeValues.noOfErrorCorrectionCodeWord; + _blocks = _qrCodeValues.noOfErrorCorrectionBlocks; + + if (_blocks.length == 6) { + errorCorrectionCodeWord.dataBits = + (_dataBits - _blocks[3] * _blocks[5]) ~/ _blocks[0]; + } else { + errorCorrectionCodeWord.dataBits = _dataBits ~/ _blocks[0]; + } + + errorCorrectionCodeWord.eccw = eccw ~/ totalBlockSize; + + final List> polynomial = + List>.generate(totalBlockSize, (int i) => []); + int count = 0; + + for (int i = 0; i < _blocks[0]; i++) { + errorCorrectionCodeWord.dataCodeWords = ds1[count]; + polynomial[count++] = errorCorrectionCodeWord.getERCW(); + } + if (_blocks.length == 6) { + errorCorrectionCodeWord.dataBits = + (_dataBits - _blocks[0] * _blocks[2]) ~/ _blocks[3]; + + for (int i = 0; i < _blocks[3]; i++) { + errorCorrectionCodeWord.dataCodeWords = ds1[count]; + polynomial[count++] = errorCorrectionCodeWord.getERCW(); + } + } + + if (_blocks.length != 6) { + for (int i = 0; i < polynomial[0].length; i++) { + for (int k = 0; k < _blocks[0]; k++) { + for (int j = 0; j < 8; j++) { + if (i < polynomial[k].length) { + _encodeDataCodeWords + .add(polynomial[k][i][j] == '1' ? true : false); + } + } + } + } + } else { + for (int i = 0; i < polynomial[0].length; i++) { + for (int k = 0; k < totalBlockSize; k++) { + for (int j = 0; j < 8; j++) { + if (i < polynomial[k].length) { + _encodeDataCodeWords + .add(polynomial[k][i][j] == '1' ? true : false); + } + } + } + } + } + } + + @override + void renderBarcode( + Canvas canvas, + Size size, + Offset offset, + String value, + Color foregroundColor, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + bool showValue) { + _encodedText = value; + _generateValues(); + + int x = 0; + final int w = _noOfModules; + final int h = _noOfModules; + + final Paint paint = getBarPaint(foregroundColor); + + final double minSize = size.width >= size.height ? size.height : size.width; + int dimension = minSize ~/ _noOfModules; + if (_qrCodeSymbology.module != null) { + dimension = _qrCodeSymbology.module; + } + final double actualSize = (_noOfModules * dimension).toDouble(); + final int xPosition = (offset.dx + (size.width - actualSize) / 2).toInt(); + int yPosition = (offset.dy + (size.height - actualSize) / 2).toInt(); + + for (int i = 0; i < w; i++) { + x = xPosition; + for (int j = 0; j < h; j++) { + if (_moduleValues[i][j].isBlack) { + paint.color = foregroundColor; + } else { + paint.color = Colors.transparent; + } + + if (_dataAllocationValues != null && + _dataAllocationValues[j][i].isFilled) { + if (_dataAllocationValues[j][i].isBlack) { + paint.color = foregroundColor; + } + } + + final Rect rect = Rect.fromLTRB(x.toDouble(), yPosition.toDouble(), + (x + dimension).toDouble(), (yPosition + dimension).toDouble()); + canvas.drawRect(rect, paint); + + x = (x + dimension).toInt(); + } + + yPosition = (yPosition + dimension).toInt(); + } + + if (showValue) { + final Offset textOffset = Offset(offset.dx, yPosition.toDouble()); + drawText( + 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]) { + 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); + double x; + double y; + switch (textAlign) { + case TextAlign.justify: + case TextAlign.center: + { + x = offset.dx + (size.width / 2 - textPainter.width / 2); + y = offset.dy + textSpacing; + } + break; + case TextAlign.left: + case TextAlign.start: + { + x = offset.dx; + y = offset.dy + textSpacing; + } + break; + case TextAlign.right: + case TextAlign.end: + { + x = offset.dx + (size.width - textPainter.width); + y = offset.dy + textSpacing; + } + break; + } + textPainter.paint(canvas, Offset(x, y)); + } +} diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/qr_code_values.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/qr_code_values.dart similarity index 97% rename from packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/qr_code_values.dart rename to packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/qr_code_values.dart index 011b2f813..133b998d9 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/qr_code_values.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/qr_code_values.dart @@ -1,9 +1,10 @@ -part of barcodes; +import '../../utils/enum.dart'; +import '../../utils/helper.dart'; /// Represents the module value -class _ModuleValue { +class ModuleValue { /// Creates the module value - _ModuleValue() { + ModuleValue() { isBlack = false; isFilled = false; isPDP = false; @@ -20,14 +21,14 @@ class _ModuleValue { } /// Represents the QR Code value -class _QRCodeValue { +class QRCodeValue { /// Creates the QR code value - _QRCodeValue({this.qrCodeVersion, this.errorCorrectionLevel}) { - _noOfDataCodeWord = _getNumberOfDataCodeWord(); - _noOfErrorCorrectionCodeWord = _getNumberOfErrorCorrectionCodeWords(); - _noOfErrorCorrectionBlocks = _getNumberOfErrorCorrectionBlocks(); - _formatInformation = _obtainFormatInformation(); - _versionInformation = _obtainVersionInformation(); + QRCodeValue({this.qrCodeVersion, this.errorCorrectionLevel}) { + noOfDataCodeWord = _getNumberOfDataCodeWord(); + noOfErrorCorrectionCodeWord = _getNumberOfErrorCorrectionCodeWords(); + noOfErrorCorrectionBlocks = _getNumberOfErrorCorrectionBlocks(); + formatInformation = _obtainFormatInformation(); + versionInformation = _obtainVersionInformation(); } /// Specifies the code version @@ -201,7 +202,7 @@ class _QRCodeValue { ]; /// Specifies the value of numeric data capacity of low error correction level - static const List _numericDataCapacityLow = [ + static const List numericDataCapacityLow = [ 41, 77, 127, @@ -246,7 +247,7 @@ class _QRCodeValue { /// Specifies the value of numeric data capacity of medium error /// correction level - static const List _numericDataCapacityMedium = [ + static const List numericDataCapacityMedium = [ 34, 63, 101, @@ -291,7 +292,7 @@ class _QRCodeValue { /// Specifies the value of numeric data capacity of quartile error /// correction level - static const List _numericDataCapacityQuartile = [ + static const List numericDataCapacityQuartile = [ 27, 48, 77, @@ -336,7 +337,7 @@ class _QRCodeValue { /// Specifies the value of numeric data capacity of high error correction /// level - static const List _numericDataCapacityHigh = [ + static const List numericDataCapacityHigh = [ 17, 34, 58, @@ -381,7 +382,7 @@ class _QRCodeValue { /// Specifies the value of alpha numeric data capacity of low error /// correction level - static const List _alphaNumericDataCapacityLow = [ + static const List alphaNumericDataCapacityLow = [ 25, 47, 77, @@ -426,7 +427,7 @@ class _QRCodeValue { /// Specifies the value of alpha numeric data capacity of high error /// correction level - static const List _alphaNumericDataCapacityMedium = [ + static const List alphaNumericDataCapacityMedium = [ 20, 38, 61, @@ -471,7 +472,7 @@ class _QRCodeValue { /// Specifies the value of alpha numeric data capacity of quartile error /// correction level - static const List _alphaNumericDataCapacityQuartile = [ + static const List alphaNumericDataCapacityQuartile = [ 16, 29, 47, @@ -516,7 +517,7 @@ class _QRCodeValue { /// Specifies the value of alpha numeric data capacity of high error /// correction level - static const List _alphaNumericDataCapacityHigh = [ + static const List alphaNumericDataCapacityHigh = [ 10, 20, 35, @@ -560,7 +561,7 @@ class _QRCodeValue { ]; /// Specifies the value of binary data capacity of low error correction level - static const List _binaryDataCapacityLow = [ + static const List binaryDataCapacityLow = [ 17, 32, 53, @@ -605,7 +606,7 @@ class _QRCodeValue { /// Specifies the value of binary data capacity of medium error correction /// level - static const List _binaryDataCapacityMedium = [ + static const List binaryDataCapacityMedium = [ 14, 26, 42, @@ -650,7 +651,7 @@ class _QRCodeValue { /// Specifies the value of binary data capacity of quartile error correction /// level - static const List _binaryDataCapacityQuartile = [ + static const List binaryDataCapacityQuartile = [ 11, 20, 32, @@ -694,7 +695,7 @@ class _QRCodeValue { ]; /// Specifies the value of binary data capacity of high error correction level - static const List _binaryDataCapacityHigh = [ + static const List binaryDataCapacityHigh = [ 7, 14, 24, @@ -738,95 +739,95 @@ class _QRCodeValue { ]; /// Returns the numeric data capacity - static int _getNumericDataCapacity( + static int getNumericDataCapacity( ErrorCorrectionLevel level, QRCodeVersion codeVersion) { List capacity; switch (level) { case ErrorCorrectionLevel.low: - capacity = _numericDataCapacityLow; + capacity = numericDataCapacityLow; break; case ErrorCorrectionLevel.medium: - capacity = _numericDataCapacityMedium; + capacity = numericDataCapacityMedium; break; case ErrorCorrectionLevel.quartile: - capacity = _numericDataCapacityQuartile; + capacity = numericDataCapacityQuartile; break; case ErrorCorrectionLevel.high: - capacity = _numericDataCapacityHigh; + capacity = numericDataCapacityHigh; break; } - final int version = _getVersionNumber(codeVersion); + final int version = getVersionNumber(codeVersion); return capacity[version - 1]; } /// Specifies the alpha numeric data capcity - static int _getAlphaNumericDataCapacity( + static int getAlphaNumericDataCapacity( ErrorCorrectionLevel level, QRCodeVersion codeVersion) { List capacity; switch (level) { case ErrorCorrectionLevel.low: - capacity = _alphaNumericDataCapacityLow; + capacity = alphaNumericDataCapacityLow; break; case ErrorCorrectionLevel.medium: - capacity = _alphaNumericDataCapacityMedium; + capacity = alphaNumericDataCapacityMedium; break; case ErrorCorrectionLevel.quartile: - capacity = _alphaNumericDataCapacityQuartile; + capacity = alphaNumericDataCapacityQuartile; break; case ErrorCorrectionLevel.high: - capacity = _alphaNumericDataCapacityHigh; + capacity = alphaNumericDataCapacityHigh; break; } - final int _version = _getVersionNumber(codeVersion); + final int _version = getVersionNumber(codeVersion); return capacity[_version - 1]; } /// Specifies the bunary data capacity - static int _getBinaryDataCapacity( + static int getBinaryDataCapacity( ErrorCorrectionLevel level, QRCodeVersion codeVersion) { List capacity; switch (level) { case ErrorCorrectionLevel.low: - capacity = _binaryDataCapacityLow; + capacity = binaryDataCapacityLow; break; case ErrorCorrectionLevel.medium: - capacity = _binaryDataCapacityMedium; + capacity = binaryDataCapacityMedium; break; case ErrorCorrectionLevel.quartile: - capacity = _binaryDataCapacityQuartile; + capacity = binaryDataCapacityQuartile; break; case ErrorCorrectionLevel.high: - capacity = _binaryDataCapacityHigh; + capacity = binaryDataCapacityHigh; break; } - final int version = _getVersionNumber(codeVersion); + final int version = getVersionNumber(codeVersion); return capacity[version - 1]; } /// Specifies the number of data code word - int _noOfDataCodeWord; + int noOfDataCodeWord; /// Specifies the number of error correction code word - int _noOfErrorCorrectionCodeWord; + int noOfErrorCorrectionCodeWord; /// Specifies the number of error correction blocks - List _noOfErrorCorrectionBlocks; + List noOfErrorCorrectionBlocks; /// Specifies the format information - List _formatInformation; + List formatInformation; /// Specifies the version information - List _versionInformation; + List versionInformation; /// Returns the alpha numeric value based on provided QR Version /// /// This is deliberately a very large method. This method could not be /// refactor to a smaller methods, since it has single switch condition and /// returns the alpha numeric value based on provided QR Version - int _getAlphaNumericValues(String value) { + int getAlphaNumericValues(String value) { int valueInInt = 0; switch (value) { case '0': @@ -1949,7 +1950,7 @@ class _QRCodeValue { /// Calculates the number of error corrcetion code words int _getNumberOfErrorCorrectionCodeWords() { - int index = (_getVersionNumber(qrCodeVersion) - 1) * 4; + int index = (getVersionNumber(qrCodeVersion) - 1) * 4; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: index += 0; @@ -1975,7 +1976,7 @@ class _QRCodeValue { /// returns the error correction blocks based om the QR code version List _getNumberOfErrorCorrectionBlocks() { List numberOfErrorCorrectionBlocks; - final int version = _getVersionNumber(qrCodeVersion); + final int version = getVersionNumber(qrCodeVersion); switch (version) { case 1: case 2: @@ -2895,7 +2896,7 @@ class _QRCodeValue { /// returns the version information List _obtainVersionInformation() { List versionInformation; - final int version = _getVersionNumber(qrCodeVersion); + final int version = getVersionNumber(qrCodeVersion); switch (version) { case 7: versionInformation = [ 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 25a6bfe4d..94005aa01 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 @@ -1,4 +1,5 @@ -part of barcodes; +import '../base/symbology_base.dart'; +import '../utils/enum.dart'; /// The [DataMatrix] is two-dimensional barcode. The information to be encoded /// with text or numeric values. @@ -19,9 +20,7 @@ class DataMatrix extends Symbology { {int module, this.dataMatrixSize = DataMatrixSize.auto, this.encoding = DataMatrixEncoding.auto}) - : super(module: module) { - _initialize(); - } + : super(module: module); /// Define the size that is used to encode the amount of data. /// @@ -74,1155 +73,4 @@ class DataMatrix extends Symbology { ///} /// ```dart final DataMatrixEncoding encoding; - - /// Defines the list of symbol attributes - List<_DataMatrixSymbolAttribute> _symbolAttributes; - - /// Defines the log list - List _log; - - /// Defines the aLog list - List _aLog; - - /// Defines the block length - int _blockLength; - - /// Defines the polynomial collection - List _polynomial; - - /// Defines the symbol attributes - _DataMatrixSymbolAttribute _symbolAttribute; - - /// Defines the actual encoding value - DataMatrixEncoding _encoding; - - /// Defines the data matrix input value - String _inputValue; - - /// Defines the list of data matrix - List> _dataMatrixList; - - /// Defines the list of encoded code words - List _encodedCodeword; - - /// Initializes the symbol attributes - /// - /// This is deliberately a very large method. This method could not be - /// refactored to a smaller methods, since the attributes corresponds to the - /// each matrix is added into the list - void _initialize() { - _symbolAttributes = <_DataMatrixSymbolAttribute>[ - _DataMatrixSymbolAttribute( - 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), - _DataMatrixSymbolAttribute( - 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), - _DataMatrixSymbolAttribute( - 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), - _DataMatrixSymbolAttribute( - 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), - _DataMatrixSymbolAttribute( - 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), - _DataMatrixSymbolAttribute( - 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), - _DataMatrixSymbolAttribute( - 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), - _DataMatrixSymbolAttribute( - 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), - _DataMatrixSymbolAttribute( - 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), - _DataMatrixSymbolAttribute( - 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), - _DataMatrixSymbolAttribute( - 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), - _DataMatrixSymbolAttribute( - 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), - - // Rectangle matrix - _DataMatrixSymbolAttribute( - 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), - _DataMatrixSymbolAttribute( - 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), - _DataMatrixSymbolAttribute( - 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) - ]; - - _createLogList(); - } - - /// Method to create the log list - void _createLogList() { - _log = List(256); - _aLog = List(256); - _log[0] = -255; - _aLog[0] = 1; - - for (int i = 1; i <= 255; i++) { - _aLog[i] = _aLog[i - 1] * 2; - - if (_aLog[i] >= 256) { - _aLog[i] = _aLog[i] ^ 301; - } - - _log[_aLog[i]] = i; - } - } - - /// method to encode the provided data - List _getData() { - return List.from(utf8.encode(_inputValue)); - } - - /// Method used to create the data matrix - void _createMatrix(List codeword) { - int x, y, numOfRows, numOfColumns; - List places; - final int width = _symbolAttribute.symbolColumn; - final int height = _symbolAttribute.symbolRow; - final int fieldWidth = width ~/ _symbolAttribute.horizontalDataRegion; - final int fieldHeight = height ~/ _symbolAttribute.verticalDataRegion; - numOfColumns = width - 2 * (width ~/ fieldWidth); - numOfRows = height - 2 * (height ~/ fieldHeight); - places = List(numOfColumns * numOfRows); - - _errorCorrectingCode200Placement(places, numOfRows, numOfColumns); - final List matrix = List(width * height); - for (y = 0; y < height; y += fieldHeight) { - for (x = 0; x < width; x++) { - matrix[y * width + x] = 1; - } - - for (x = 0; x < width; x += 2) { - matrix[(y + fieldHeight - 1) * width + x] = 1; - } - } - - for (x = 0; x < width; x += fieldWidth) { - for (y = 0; y < height; y++) { - matrix[y * width + x] = 1; - } - - for (y = 0; y < height; y += 2) { - matrix[y * width + x + fieldWidth - 1] = 1; - } - } - - for (y = 0; y < numOfRows; y++) { - for (x = 0; x < numOfColumns; x++) { - final int v = places[(numOfRows - y - 1) * numOfColumns + x]; - if (v == 1 || v > 7 && (codeword[(v >> 3) - 1] & (1 << (v & 7))) != 0) { - matrix[(1 + y + 2 * (y ~/ (fieldHeight - 2))) * width + - 1 + - x + - 2 * (x ~/ (fieldWidth - 2))] = 1; - } - } - } - - _createArray(matrix); - } - - /// Methods to create array based on row and column - void _createArray(List matrix) { - final int symbolColumn = _symbolAttribute.symbolColumn, - symbolRow = _symbolAttribute.symbolRow; - - final List> tempArray = - List>.generate(symbolColumn, (int j) => List(symbolRow)); - - for (int m = 0; m < symbolColumn; m++) { - for (int n = 0; n < symbolRow; n++) { - tempArray[m][n] = matrix[symbolColumn * n + m]; - } - } - - final List> tempArray2 = - List>.generate(symbolRow, (int j) => List(symbolColumn)); - - for (int i = 0; i < symbolRow; i++) { - for (int j = 0; j < symbolColumn; j++) { - tempArray2[symbolRow - 1 - i][j] = tempArray[j][i]; - } - } - - _addQuietZone(tempArray2); - } - - /// Method used to add the quiet zone - void _addQuietZone(List> tempArray) { - const int quietZone = 1; - final int w = _symbolAttribute.symbolRow + (2 * quietZone); - final int h = _symbolAttribute.symbolColumn + (2 * quietZone); - _dataMatrixList = List>.generate(w, (int j) => List(h)); - // Top quietzone. - for (int i = 0; i < h; i++) { - _dataMatrixList[0][i] = 0; - } - - for (int i = quietZone; i < w - quietZone; i++) { - _dataMatrixList[i][0] = 0; - - for (int j = quietZone; j < h - quietZone; j++) { - _dataMatrixList[i][j] = tempArray[i - quietZone][j - quietZone]; - } - - // Right quietzone. - _dataMatrixList[i][h - quietZone] = 0; - } - - for (int i = 0; i < h; i++) { - _dataMatrixList[w - quietZone][i] = 0; - } - } - - /// Method to encode the error correcting code word - 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); - } - - if (column < 0) { - column += numOfColumns; - row += 4 - ((numOfColumns + 4) % 8); - } - - array[row * numOfColumns + column] = (place << 3) + character.codeUnitAt(0); - } - - /// 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)); - } - - /// 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)); - } - - /// 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)); - } - - /// 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)); - } - - /// 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)); - } - - /// Method to encode the error correcting code word - void _errorCorrectingCode200Placement( - List array, int numOfRows, int numOfColumns) { - int row, column, place; - for (row = 0; row < numOfRows; row++) { - for (column = 0; column < numOfColumns; column++) { - array[row * numOfColumns + column] = 0; - } - } - - place = 1; - row = 4; - column = 0; - do { - if (row == numOfRows && !(column != 0)) { - _errorCorrectingCode200PlacementCornerA( - array, numOfRows, numOfColumns, place++); - } - - if ((row == numOfRows - 2) && - !(column != 0) && - ((numOfColumns % 4) != 0)) { - _errorCorrectingCode200PlacementCornerB( - array, numOfRows, numOfColumns, place++); - } - - if (row == numOfRows - 2 && !(column != 0) && (numOfColumns % 8) == 4) { - _errorCorrectingCode200PlacementCornerC( - array, numOfRows, numOfColumns, place++); - } - - if (row == numOfRows + 4 && column == 2 && !((numOfColumns % 8) != 0)) { - _errorCorrectingCode200PlacementCornerD( - array, numOfRows, numOfColumns, place++); - } - // enocoding placement (up/right) - do { - if (row < numOfRows && - column >= 0 && - !(array[row * numOfColumns + column] != 0)) { - _errorCorrectingCode200PlacementBlock( - array, numOfRows, numOfColumns, row, column, place++); - } - - row -= 2; - column += 2; - } while (row >= 0 && column < numOfColumns); - row++; - column += 3; - // enocoding placement (down/left) - do { - if (row >= 0 && - column < numOfColumns && - !(array[row * numOfColumns + column] != 0)) { - _errorCorrectingCode200PlacementBlock( - array, numOfRows, numOfColumns, row, column, place++); - } - - row += 2; - column -= 2; - } while (row < numOfRows && column >= 0); - row += 3; - column++; - } while (row < numOfRows || column < numOfColumns); - if (!(array[numOfRows * numOfColumns - 1] != 0)) { - array[numOfRows * numOfColumns - 1] = - array[numOfRows * numOfColumns - numOfColumns - 2] = 1; - } - } - - /// Method to double the error correcting code - int _getErrorCorrectingCodeDoublify(int i, int j) { - if (i == 0) { - return 0; - } - - if (j == 0) { - return i; - } - - return _aLog[(_log[i] + j) % 255]; - } - - /// Method used for error correction - int _getErrorCorrectingCodeProduct(int i, int j) { - if (i == 0 || j == 0) { - return 0; - } else if (i > 255 || j > 255) { - return 0; - } - return _aLog[(_log[i] + _log[j]) % 255]; - } - - /// Method to perform the XOR operation for error correction - int _getErrorCorrectingCodeSum(int i, int j) { - return i ^ j; - } - - /// Method to pad the code word - List _getPaddedCodewords(int dataCWLength, List temp) { - final List codewords = []; - int length = temp.length; - for (int i = 0; i < length; i++) { - codewords.add(temp[i]); - } - - if (length < dataCWLength) { - codewords.add(129); - } - - length = codewords.length; - while (length < dataCWLength) { - int _value = 129 + (((length + 1) * 149) % 253) + 1; - if (_value > 254) { - _value -= 254; - } - - codewords.add(_value); - length = codewords.length; - } - - return codewords; - } - - /// Method used for base256 encoding - List _getDataMatrixBaseEncoder(List dataCodeword) { - int num = 1; - if (dataCodeword.length > 249) { - num++; - } - - final List result = List((1 + num) + dataCodeword.length); - result[0] = 231; - if (dataCodeword.length <= 249) { - result[1] = dataCodeword.length; - } else { - result[1] = ((dataCodeword.length / 250) + 249).toInt(); - result[2] = dataCodeword.length % 250; - } - - for (int i = 0; i < dataCodeword.length; i++) { - result[(1 + num) + i] = dataCodeword[i]; - } - - for (int i = 1; i < result.length; i++) { - result[i] = _getBase256Codeword(result[i], i); - } - - return result; - } - - /// Method to compute the code word - int _getBase256Codeword(int codewordValue, int index) { - final int i = ((149 * (index + 1)) % 255) + 1; - final int j = codewordValue + i; - if (j <= 255) { - return j; - } - - return j - 256; - } - - /// Method used for ASCII numeric encoding - List _getDataMatrixASCIINumericEncoder(List dataCodeword) { - List destinationArray = dataCodeword; - bool isEven = true; - // if the input data length is odd, add 0 in front of the data. - if ((destinationArray.length % 2) == 1) { - isEven = false; - destinationArray = List(dataCodeword.length + 1); - for (int i = 0; i < dataCodeword.length; i++) { - destinationArray[i] = dataCodeword[i]; - } - } - - final List result = List(destinationArray.length ~/ 2); - 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) + - (destinationArray[(2 * i) + 1] - 48)) + - 130; - } - } - return result; - } - - /// Method used for ASCII encoding - List _getDataMatrixASCIIEncoder(List dataCodeword) { - final List result = List.from(dataCodeword); - int index = 0; - for (int i = 0; i < dataCodeword.length; i++) { - if (dataCodeword[i] >= 48 && dataCodeword[i] <= 57) { - int prevIndex = 0; - if (i != 0) { - prevIndex = index - 1; - } - - final int prevValue = result[prevIndex] - 1; - int priorValue = 0; - if (i != 0 && index != 1) { - priorValue = result[prevIndex - 1]; - } - - //Check the priorvalue is digit or non convertable value.if it is true - // then combine the 2 digit - if (priorValue != 235 && prevValue >= 48 && prevValue <= 57) { - result[prevIndex] = - 10 * (prevValue - 0) + (dataCodeword[i] - 0) + 130; - } else { - result[index++] = dataCodeword[i] + 1; - } - } else if (dataCodeword[i] < 127) { - result[index++] = dataCodeword[i] + 1; - } else { - //Assign 235 default value for non - // convertable values(other than digits,letters and special characters - // 0 to 127 asciii values) - result[index] = 235; - result[index++] = dataCodeword[i] - 127; - } - } - - final List encodedData = List.from(result); - return encodedData; - } - - /// Method used for computing error correction - List _getComputedErrorCorrection(List codeword) { - _setSymbolAttribute(codeword); - final int numCorrectionCodeword = _symbolAttribute.correctionCodeWords; - // Create error correction array. - final List correctionCodeWordArray = - List(numCorrectionCodeword + _symbolAttribute.dataCodeWords); - for (int i = 0; i < correctionCodeWordArray.length; i++) { - correctionCodeWordArray[i] = 0; - } - - final int step = _symbolAttribute.interLeavedBlock; - final int symbolDataWords = _symbolAttribute.dataCodeWords; - final int blockErrorWords = _symbolAttribute.correctionCodeWords ~/ step; - final int total = symbolDataWords + blockErrorWords * step; - // Updates m_polynomial based on the required number of correction bytes. - _createPolynomial(step); - //set block length (each block consists of length 68 ) - _blockLength = 68; - final List blockByte = List(_blockLength); - for (int block = 0; block < step; block++) { - for (int k = 0; k < blockByte.length; k++) { - blockByte[k] = 0; - } - - for (int i = block; i < symbolDataWords; i += step) { - final int val = _getErrorCorrectingCodeSum( - blockByte[blockErrorWords - 1], _encodedCodeword[i]); - for (int j = blockErrorWords - 1; j > 0; j--) { - blockByte[j] = _getErrorCorrectingCodeSum(blockByte[j - 1], - _getErrorCorrectingCodeProduct(_polynomial[j], val)); - } - - blockByte[0] = _getErrorCorrectingCodeProduct(_polynomial[0], val); - } - - int blockDataWords = 0; - if (block >= 8 && dataMatrixSize == DataMatrixSize.size144x144) { - blockDataWords = _symbolAttribute.dataCodeWords ~/ step; - } else { - blockDataWords = _symbolAttribute.interLeavedDataBlock; - - int bIndex = blockErrorWords; - - for (int i = block + (step * blockDataWords); i < total; i += step) { - correctionCodeWordArray[i] = blockByte[--bIndex]; - } - - if (bIndex != 0) { - throw 'Error in error correction code generation!'; - } - } - } - - return _getCorrectionCodeWordArray( - correctionCodeWordArray, numCorrectionCodeword); - } - - /// Method to get the correction code word - List _getCorrectionCodeWordArray( - List correctionCodeWordArray, int numCorrectionCodeword) { - if (correctionCodeWordArray.length > numCorrectionCodeword) { - final List tmp = correctionCodeWordArray; - correctionCodeWordArray = List(numCorrectionCodeword); - for (int i = 0; i < correctionCodeWordArray.length; i++) { - correctionCodeWordArray[i] = 0; - } - int z = 0; - - for (int i = tmp.length - 1; i > _symbolAttribute.dataCodeWords; i--) { - correctionCodeWordArray[z++] = tmp[i]; - } - } - - final List reversedList = correctionCodeWordArray.reversed.toList(); - return reversedList; - } - - /// Method to set the symbol attribute - void _setSymbolAttribute(List codeword) { - int dataLength = codeword.length; - _symbolAttribute = _DataMatrixSymbolAttribute(); - if (dataMatrixSize == DataMatrixSize.auto) { - for (int i = 0; i < _symbolAttributes.length; i++) { - final _DataMatrixSymbolAttribute attribute = _symbolAttributes[i]; - if (attribute.dataCodeWords >= dataLength) { - _symbolAttribute = attribute; - break; - } - } - } else { - _symbolAttribute = - _symbolAttributes[_getDataMatrixSize(dataMatrixSize) - 1]; - } - - List temp; - // Pad data codeword if the length is less than the selected symbol - // attribute. - if (_symbolAttribute.dataCodeWords > dataLength) { - temp = _getPaddedCodewords(_symbolAttribute.dataCodeWords, codeword); - _encodedCodeword = List.from(temp); - dataLength = codeword.length; - } else if (_symbolAttribute.dataCodeWords == 0) { - throw 'Data cannot be encoded as barcode'; - } else if (_symbolAttribute.dataCodeWords < dataLength) { - final String symbolRow = _symbolAttribute.symbolRow.toString(); - final String symbolColumn = _symbolAttribute.symbolColumn.toString(); - throw 'Data too long for $symbolRow x $symbolColumn barcode.'; - } - } - - /// Method used to create the polynomial - void _createPolynomial(int step) { - //Set block length for polynomial values - _blockLength = 69; - _polynomial = List(_blockLength); - final int blockErrorWords = _symbolAttribute.correctionCodeWords ~/ step; - for (int i = 0; i < _polynomial.length; i++) { - _polynomial[i] = 0x01; - } - - for (int i = 1; i <= blockErrorWords; i++) { - for (int j = i - 1; j >= 0; j--) { - _polynomial[j] = _getErrorCorrectingCodeDoublify(_polynomial[j], i); - if (j > 0) { - _polynomial[j] = - _getErrorCorrectingCodeSum(_polynomial[j], _polynomial[j - 1]); - } - } - } - } - - /// Method to get the code word - List _getCodeword(List dataCodeword) { - _encodedCodeword = _getDataCodeword(dataCodeword); - final List correctCodeword = - _getComputedErrorCorrection(_encodedCodeword); - final List finalCodeword = - List(_encodedCodeword.length + correctCodeword.length); - for (int i = 0; i < _encodedCodeword.length; i++) { - finalCodeword[i] = _encodedCodeword[i]; - } - - for (int i = 0; i < correctCodeword.length; i++) { - finalCodeword[i + _encodedCodeword.length] = correctCodeword[i]; - } - - return finalCodeword; - } - - /// Method used to prepare the code word - List _getDataCodeword(List dataCodeword) { - _encoding = encoding; - if (encoding == DataMatrixEncoding.auto || - encoding == DataMatrixEncoding.asciiNumeric) { - bool number = true; - bool extended = false; - int num = 0; - final List data = dataCodeword; - DataMatrixEncoding actualEncoding = DataMatrixEncoding.ascii; - for (int i = 0; i < data.length; i++) { - if ((data[i] < 48) || (data[i] > 57)) { - number = false; - } else if (data[i] > 127) { - num++; - if (num > 3) { - extended = true; - break; - } - } - } - - if (number) { - actualEncoding = DataMatrixEncoding.asciiNumeric; - } - - if (extended) { - actualEncoding = DataMatrixEncoding.base256; - } - - if (actualEncoding == DataMatrixEncoding.asciiNumeric && - encoding != actualEncoding) { - throw 'Data contains invalid characters and cannot be encoded as' - 'ASCIINumeric.'; - } - - _encoding = actualEncoding; - } - - return _getEncoding(dataCodeword); - } - - /// Method to get the encoding type - List _getEncoding(List dataCodeword) { - List result; - switch (_encoding) { - case DataMatrixEncoding.ascii: - result = _getDataMatrixASCIIEncoder(dataCodeword); - break; - case DataMatrixEncoding.asciiNumeric: - result = _getDataMatrixASCIINumericEncoder(dataCodeword); - break; - case DataMatrixEncoding.base256: - result = _getDataMatrixBaseEncoder(dataCodeword); - break; - case DataMatrixEncoding.auto: - break; - } - - return result; - } - - @override - bool _getIsValidateInput(String value) { - return true; - } - - @override - void _renderBarcode( - Canvas canvas, - Size size, - Offset offset, - String value, - Color foregroundColor, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - bool showValue) { - _inputValue = value; - - _buildDataMatrix(); - const int quietZone = 1; - double x = 0; - double y = 0; - final Paint paint = _getBarPaint(foregroundColor); - final int w = _symbolAttribute.symbolRow + (2 * quietZone); - final int h = _symbolAttribute.symbolColumn + (2 * quietZone); - final double minSize = size.width >= size.height ? size.height : size.width; - final bool isSquareMatrix = _getDataMatrixSize(dataMatrixSize) < 25; - int dimension = minSize ~/ _dataMatrixList.length; - final int rectDimension = minSize ~/ _dataMatrixList[0].length; - final int xDimension = - module ?? (isSquareMatrix ? dimension : rectDimension); - dimension = module ?? dimension; - for (int i = 0; i < w; i++) { - x = 0; - for (int j = 0; j < h; j++) { - x += xDimension; - } - - y += dimension; - } - - final double xPosition = offset.dx + (size.width - x) / 2; - double yPosition = offset.dy + (size.height - y) / 2; - - for (int i = 0; i < w; i++) { - x = xPosition; - for (int j = 0; j < h; j++) { - if (_dataMatrixList[i][j] == 1) { - final Rect matrixRect = Rect.fromLTRB( - x, yPosition, x + xDimension, yPosition + dimension); - canvas.drawRect(matrixRect, paint); - } - - x += xDimension; - } - - yPosition += dimension; - } - - if (showValue) { - final Offset textOffset = Offset(offset.dx, yPosition.toDouble()); - _drawText( - 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]) { - 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); - double x; - double y; - switch (textAlign) { - case TextAlign.justify: - case TextAlign.center: - { - x = offset.dx + (size.width / 2 - textPainter.width / 2); - y = offset.dy + textSpacing; - } - break; - case TextAlign.left: - case TextAlign.start: - { - x = offset.dx; - y = offset.dy + textSpacing; - } - break; - case TextAlign.right: - case TextAlign.end: - { - x = offset.dx + (size.width - textPainter.width); - y = offset.dy + textSpacing; - } - break; - } - textPainter.paint(canvas, Offset(x, y)); - } - - void _buildDataMatrix() { - final List codeword = _getCodeword(_getData()); - _createMatrix(codeword); - } -} - -/// 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}); - - /// Defines the symbol row - final int symbolRow; - - /// Defines the symbol column - final int symbolColumn; - - /// Defines the horizontal data region - final int horizontalDataRegion; - - /// Defines the vertical data region - final int verticalDataRegion; - - /// Defines the data code words - final int dataCodeWords; - - /// Defines the error correction code words - final int correctionCodeWords; - - /// Defines the inter leaved blocks - final int interLeavedBlock; - - /// Defines the inter leaved data blocks - final int interLeavedDataBlock; } 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 1f47d2cd4..a8b3ec484 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 @@ -1,4 +1,5 @@ -part of barcodes; +import '../base/symbology_base.dart'; +import '../utils/enum.dart'; /// Quick response code ([QRCode]) is a two-dimensional barcode. It can /// efficiently store more information in a smaller space than 1D barcodes. @@ -34,28 +35,7 @@ class QRCode extends Symbology { this.errorCorrectionLevel = ErrorCorrectionLevel.high, this.inputMode = QRInputMode.binary, int module}) - : super(module: module) { - if (codeVersion != null && codeVersion != QRCodeVersion.auto) { - _codeVersion = codeVersion; - _isUserMentionedVersion = true; - } else { - _isUserMentionedVersion = false; - } - - if (errorCorrectionLevel != null) { - _errorCorrectionLevel = errorCorrectionLevel; - _isUserMentionedErrorCorrectionLevel = true; - } else { - _isUserMentionedErrorCorrectionLevel = false; - } - - if (inputMode != null) { - _inputMode = inputMode; - _isUserMentionedMode = true; - } else { - _isUserMentionedMode = false; - } - } + : super(module: module); /// Define the version that is used to encode the amount of data. /// @@ -123,1925 +103,4 @@ class QRCode extends Symbology { ///} /// ```dart final QRInputMode inputMode; - - /// Specifies the CP437 character set - static const List _cp437CharSet = [ - '2591', - '2592', - '2593', - '2502', - '2524', - '2561', - '2562', - '2556', - '2555', - '2563', - '2551', - '2557', - '255D', - '255C', - '255B', - '2510', - '2514', - '2534', - '252C', - '251C', - '2500', - '253C', - '255E', - '255F', - '255A', - '2554', - '2569', - '2566', - '2560', - '2550', - '256C', - '2567', - '2568', - '2564', - '2565', - '2559', - '2558', - '2552', - '2553', - '256B', - '256A', - '2518', - '250C', - '2588', - '2584', - '258C', - '2590', - '2580', - '25A0' - ]; - - /// Specifies the latin2 character set - static const List _latin2CharSet = [ - '104', - '2D8', - '141', - '13D', - '15A', - '160', - '15E', - '164', - '179', - '17D', - '17B', - '105', - '2DB', - '142', - '13E', - '15B', - '2C7', - '161', - '15F', - '165', - '17A', - '2DD', - '17E', - '17C', - '154', - '102', - '139', - '106', - '10C', - '118', - '11A', - '10E', - '110', - '143', - '147', - '150', - '158', - '16E', - '170', - '162', - '155', - '103', - '13A', - '107', - '10D', - '119', - '11B', - '10F', - '111', - '144', - '148', - '151', - '159', - '16F', - '171', - '163', - '2D9' - ]; - - /// Specifies the latin3 character set - static const List _latin3CharSet = [ - '126', - '124', - '130', - '15E', - '11E', - '134', - '17B', - '127', - '125', - '131', - '15F', - '11F', - '135', - '17C', - '10A', - '108', - '120', - '11C', - '16C', - '15C', - '10B', - '109', - '121', - '11D', - '16D', - '15D' - ]; - - /// Specifies the latin4 character set - static const List _latin4CharSet = [ - '104', - '138', - '156', - '128', - '13B', - '160', - '112', - '122', - '166', - '17D', - '105', - '2DB', - '157', - '129', - '13C', - '2C7', - '161', - '113', - '123', - '167', - '14A', - '17E', - '14B', - '100', - '12E', - '10C', - '118', - '116', - '12A', - '110', - '145', - '14C', - '136', - '172', - '168', - '16A', - '101', - '12F', - '10D', - '119', - '117', - '12B', - '111', - '146', - '14D', - '137', - '173', - '169', - '16B' - ]; - - /// Specifies the windows 1250 character set - static const List _windows1250CharSet = [ - '141', - '104', - '15E', - '17B', - '142', - '105', - '15F', - '13D', - '13E', - '17C' - ]; - - /// Specifies the windows 1251 character set - static const List _windows1251CharSet = [ - '402', - '403', - '453', - '409', - '40A', - '40C', - '40B', - '40F', - '452', - '459', - '45A', - '45C', - '45B', - '45F', - '40E', - '45E', - '408', - '490', - '401', - '404', - '407', - '406', - '456', - '491', - '451', - '454', - '458', - '405', - '455', - '457' - ]; - - /// Specifies the windows 1252 character set - static const List _windows1252CharSet = [ - '20AC', - '201A', - '192', - '201E', - '2026', - '2020', - '202', - '2C6', - '2030', - '160', - '2039', - '152', - '17D', - '2018', - '2019', - '201C', - '201D', - '2022', - '2013', - '2014', - '2DC', - '2122', - '161', - '203A', - '153', - '17E', - '178' - ]; - - /// Specifies the QR code value - _QRCodeValue _qrCodeValues; - - /// Specifies the QR code version - QRCodeVersion _codeVersion = QRCodeVersion.version01; - - /// Species the module value - int _noOfModules = 21; - - /// Specifies the list of module value - List> _moduleValues; - - /// Specifies the data alllocation value - List> _dataAllocationValues; - - /// Specifies the actual input mode - QRInputMode _inputMode = QRInputMode.numeric; - - /// Specifies the actula error corrceton level - ErrorCorrectionLevel _errorCorrectionLevel = ErrorCorrectionLevel.low; - - /// Specifies the dat bits value - int _dataBits = 0; - - /// Specifies the list of blocks - List _blocks; - - /// Specifies the user mentioned level - bool _isUserMentionedMode = false; - - /// Specifies the version is mentioned by user - bool _isUserMentionedVersion = false; - - /// Specifies the error correction level is mentioned by user - bool _isUserMentionedErrorCorrectionLevel = false; - - /// Specifies the bool value based on the error correction input - bool _isEci = false; - - /// Specifies the assignment number - int _eciAssignmentNumber = 3; - - /// Specifies the text value - String _encodedText; - - /// Specifies the list of encode data - List _encodeDataCodeWords; - - @override - bool _getIsValidateInput(String value) { - return true; - } - - /// Specifies the character set of CP437 - bool _getIsCP437Character(int inputChar) { - final String inputCharIndex = inputChar.toRadixString(16); - if (_cp437CharSet.contains(inputCharIndex)) { - return true; - } - - return false; - } - - /// Gets the input character is present in ISO8859_2 character set - bool _getIsISO8859_2CharacterSet(int input) { - final String inputInHex = input.toRadixString(16); - if (_latin2CharSet.contains(inputInHex)) { - return true; - } - return false; - } - - /// Checks the input character is present in ISO8859_3 character set - bool _getIsISO8859_3CharacterSet(int input) { - final String inputInHex = input.toRadixString(16); - - if (_latin3CharSet.contains(inputInHex)) { - return true; - } - return false; - } - - /// Checks the input character is present in ISO8859_4 character set - bool _getIsISO8859_4CharacterSet(int input) { - final String inputInHex = input.toRadixString(16); - if (_latin4CharSet.contains(inputInHex)) { - return true; - } - return false; - } - - /// Checks the input character is present in ISO8859_4 character set - bool _getIsISO8859_5CharacterSet(int input) { - if (input >= 1025 && - input <= 1119 && - input != 1037 && - input != 1104 && - input != 1117) { - return true; - } - - return false; - } - - ///Checks the input character is present in ISO8859_5 character set - bool _getIsISO8859_6CharacterSet(int input) { - if ((input >= 1569 && input <= 1594) || - (input >= 1600 && input <= 1618) || - input == 1567 || - input == 1563 || - input == 1548) { - return true; - } - - return false; - } - - ///Checks the input character is present in ISO8859_7 character set - bool _getIsISO8859_7CharacterSet(int input) { - if ((input >= 900 && input <= 974) || input == 890) { - return true; - } - - return false; - } - - /// Checks the input character is present in ISO8859_8 character set - bool _getIsISO8859_8CharacterSet(int input) { - if (input >= 1488 && input <= 1514) { - return true; - } - - return false; - } - - /// Checks the input character is present in ISO8859_811character set - bool _getIsISO8859_11CharacterSet(int input) { - if (input >= 3585 && input <= 3675) { - return true; - } - - return false; - } - - /// Checks the input character is present in Windows1250Character set - bool _getIsWindows1250Character(int input) { - final String inputCharHex = input.toRadixString(16); - if (_windows1250CharSet.contains(inputCharHex)) { - return true; - } - - if (input >= 1569 && input <= 1610) { - return true; - } - - return false; - } - - /// Checks the input character is present in Windows1251Character set - bool _getIsWindows1251Character(int input) { - final String inputChar = input.toRadixString(16); - if (_windows1251CharSet.contains(inputChar)) { - return true; - } - - if (input >= 1040 && input <= 1103) { - return true; - } - - return false; - } - - /// Checks the input character is present in Windows1252Character set - bool _getIsWindows1252Character(int input) { - final String inputChar = input.toRadixString(16); - - if (_windows1252CharSet.contains(inputChar)) { - return true; - } - - return false; - } - - /// Checks the input character is present in Windows1256Character set - bool _getIsWindows1256Character(int input) { - final String inputChar = input.toRadixString(16); - final List windows1256CharSet = [ - '67E', - '679', - '152', - '686', - '698', - '688', - '6AF', - '6A9', - '691', - '153', - '6BA', - '6BE', - '6C1' - ]; - - if (windows1256CharSet.contains(inputChar)) { - return true; - } - - if (input >= 1569 && input <= 1610) { - return true; - } - - return false; - } - - /// Allocates the format and the version information - void _allocateFormatAndVersionInformation() { - for (int i = 0; i < 9; i++) { - _moduleValues[8][i].isFilled = true; - _moduleValues[i][8].isFilled = true; - } - - for (int i = _noOfModules - 8; i < _noOfModules; i++) { - _moduleValues[8][i].isFilled = true; - _moduleValues[i][8].isFilled = true; - } - - final int version = _getVersionNumber(_codeVersion); - if (version > 6) { - final List versionInformation = _qrCodeValues._versionInformation; - int count = 0; - for (int i = 0; i < 6; i++) { - for (int j = 2; j >= 0; j--) { - _moduleValues[i][_noOfModules - 9 - j].isBlack = - versionInformation != null && versionInformation[count] == 1 - ? true - : false; - _moduleValues[i][_noOfModules - 9 - j].isFilled = true; - - _moduleValues[_noOfModules - 9 - j][i].isBlack = - versionInformation != null && versionInformation[count++] == 1 - ? true - : false; - _moduleValues[_noOfModules - 9 - j][i].isFilled = true; - } - } - } - } - - /// Returns the alignment pattern - /// - /// This is a very large method. This method could not be - /// refactored to a smaller methods, since it has single switch condition and - /// returns the alignment pattern based on the QR Code version - List _getAlignmentPatternCoordinates() { - List align; - final int versionNumber = _getVersionNumber(_codeVersion); - switch (versionNumber) { - case 02: - align = [6, 18]; - break; - case 03: - align = [6, 22]; - break; - case 04: - align = [6, 26]; - break; - case 05: - align = [6, 30]; - break; - case 06: - align = [6, 34]; - break; - case 07: - align = [6, 22, 38]; - break; - case 08: - align = [6, 24, 42]; - break; - case 09: - align = [6, 26, 46]; - break; - case 10: - align = [6, 28, 50]; - break; - case 11: - align = [6, 30, 54]; - break; - case 12: - align = [6, 32, 58]; - break; - case 13: - align = [6, 34, 62]; - break; - case 14: - align = [6, 26, 46, 66]; - break; - case 15: - align = [6, 26, 48, 70]; - break; - case 16: - align = [6, 26, 50, 74]; - break; - case 17: - align = [6, 30, 54, 78]; - break; - case 18: - align = [6, 30, 56, 82]; - break; - case 19: - align = [6, 30, 58, 86]; - break; - case 20: - align = [6, 34, 62, 90]; - break; - case 21: - align = [6, 28, 50, 72, 94]; - break; - case 22: - align = [6, 26, 50, 74, 98]; - break; - case 23: - align = [6, 30, 54, 78, 102]; - break; - case 24: - align = [6, 28, 54, 80, 106]; - break; - case 25: - align = [6, 32, 58, 84, 110]; - break; - case 26: - align = [6, 30, 58, 86, 114]; - break; - case 27: - align = [6, 34, 62, 90, 118]; - break; - case 28: - align = [6, 26, 50, 74, 98, 122]; - break; - case 29: - align = [6, 30, 54, 78, 102, 126]; - break; - case 30: - align = [6, 26, 52, 78, 104, 130]; - break; - case 31: - align = [6, 30, 56, 82, 108, 134]; - break; - case 32: - align = [6, 34, 60, 86, 112, 138]; - break; - case 33: - align = [6, 30, 58, 86, 114, 142]; - break; - case 34: - align = [6, 34, 62, 90, 118, 146]; - break; - case 35: - align = [6, 30, 54, 78, 102, 126, 150]; - break; - case 36: - align = [6, 24, 50, 76, 102, 128, 154]; - break; - case 37: - align = [6, 28, 54, 80, 106, 132, 158]; - break; - case 38: - align = [6, 32, 58, 84, 110, 136, 162]; - break; - case 39: - align = [6, 26, 54, 82, 110, 138, 166]; - break; - case 40: - align = [6, 30, 58, 86, 114, 142, 170]; - break; - } - return align; - } - - /// Converts the string to the list of bool - List _getStringToBoolArray(String numberInString, int noOfBits) { - final List numberInBool = List(noOfBits); - int j = 0; - for (int i = 0; i < numberInString.length; i++) { - j = j * 10 + numberInString[i].codeUnitAt(0) - 48; - } - - for (int i = 0; i < noOfBits; i++) { - numberInBool[noOfBits - i - 1] = ((j >> i) & 1) == 1; - } - return numberInBool; - } - - /// Converts the integer to bool array value - List _getIntToBoolArray(int number, int noOfBits) { - final List numberInBool = List(noOfBits); - for (int i = 0; i < noOfBits; i++) { - numberInBool[noOfBits - i - 1] = ((number >> i) & 1) == 1; - } - - return numberInBool; - } - - ///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(encodeData.length ~/ 8 ~/ noOfBlocks)); - - String stringValue = ''; - int j = 0; - int i = 0; - int blockNumber = 0; - for (; j < encodeData.length; j++) { - if (j % 8 == 0 && j != 0) { - encodedBlocks[blockNumber][i] = stringValue; - stringValue = ''; - i++; - - if (i == (encodeData.length / noOfBlocks / 8)) { - blockNumber++; - i = 0; - } - } - - stringValue += encodeData[j] ? '1' : '0'; - } - - encodedBlocks[blockNumber][i] = stringValue; - return encodedBlocks; - } - - /// Method to the split code word - List _splitCodeWord( - List> encodeData, int block, int count) { - final List encodeDataString = List(count); - for (int i = 0; i < count; i++) { - encodeDataString[i] = encodeData[block][i]; - } - - return encodeDataString; - } - - /// Method to find the actual mode, correction level and the input level - void _initialize() { - QRInputMode actualMode = QRInputMode.numeric; - for (int i = 0; i < _encodedText.length; i++) { - if (_encodedText[i].codeUnitAt(0) < 58 && - _encodedText[i].codeUnitAt(0) > 47) { - } else if ((_encodedText[i].codeUnitAt(0) < 91 && - _encodedText[i].codeUnitAt(0) > 64) || - _encodedText[i] == '\$' || - _encodedText[i] == '%' || - _encodedText[i] == '*' || - _encodedText[i] == '+' || - _encodedText[i] == '-' || - _encodedText[i] == '.' || - _encodedText[i] == '/' || - _encodedText[i] == ':' || - _encodedText[i] == ' ') { - actualMode = QRInputMode.alphaNumeric; - } else if ((_encodedText[i].codeUnitAt(0) >= 65377 && - _encodedText[i].codeUnitAt(0) <= 65439) || - (_encodedText[i].codeUnitAt(0) >= 97 && - _encodedText[i].codeUnitAt(0) <= 122)) { - actualMode = QRInputMode.binary; - - break; - } else { - actualMode = QRInputMode.binary; - _isEci = true; - break; - } - } - - if (_isUserMentionedMode) { - if (actualMode != inputMode) { - if (((actualMode == QRInputMode.alphaNumeric || - actualMode == QRInputMode.binary) && - inputMode == QRInputMode.numeric) || - (actualMode == QRInputMode.binary && - inputMode == QRInputMode.alphaNumeric)) { - // new BarcodeException("Mode Conflict"); - } - } - } - - _inputMode = actualMode; - _initializeECI(); - _initializeVersionAndECR(); - _noOfModules = (_getVersionNumber(_codeVersion) - 1) * 4 + 21; - } - - /// Method to initialize the error correction level - /// - /// This is a very large method. This method could not be - /// refactored to a smaller methods, since it has single for loop and - /// returns the ECI based on the encoded text - void _initializeECI() { - if (_isEci) { - for (int i = 0; i < _encodedText.length; i++) { - if (_encodedText[i].codeUnitAt(0) >= 32 && - _encodedText[i].codeUnitAt(0) <= 255) { - continue; - } - //Check for CP437 - if (_getIsCP437Character(_encodedText[i].codeUnitAt(0))) { - _eciAssignmentNumber = 2; - break; - } - - //Check for ISO/IEC 8859-2 - else if (_getIsISO8859_2CharacterSet(_encodedText[i].codeUnitAt(0))) { - _eciAssignmentNumber = 4; - break; - } - //Check for ISO/IEC 8859-3 - else if (_getIsISO8859_3CharacterSet(_encodedText[i].codeUnitAt(0))) { - _eciAssignmentNumber = 5; - break; - } - //Check for ISO/IEC 8859-4 - else if (_getIsISO8859_4CharacterSet(_encodedText[i].codeUnitAt(0))) { - _eciAssignmentNumber = 6; - break; - } - //Check for ISO/IEC 8859-5 - else if (_getIsISO8859_5CharacterSet(_encodedText[i].codeUnitAt(0))) { - _eciAssignmentNumber = 7; - break; - } - //Check for ISO/IEC 8859-6 - else if (_getIsISO8859_6CharacterSet(_encodedText[i].codeUnitAt(0))) { - _eciAssignmentNumber = 8; - break; - } - //Check for ISO/IEC 8859-7 - else if (_getIsISO8859_7CharacterSet(_encodedText[i].codeUnitAt(0))) { - _eciAssignmentNumber = 9; - break; - } - //Check for ISO/IEC 8859-8 - else if (_getIsISO8859_8CharacterSet(_encodedText[i].codeUnitAt(0))) { - _eciAssignmentNumber = 10; - break; - } - //Check for ISO/IEC 8859-8 - else if (_getIsISO8859_11CharacterSet(_encodedText[i].codeUnitAt(0))) { - _eciAssignmentNumber = 13; - break; - } - - //Check for Windows1250 - else if (_getIsWindows1250Character(_encodedText[i].codeUnitAt(0))) { - _eciAssignmentNumber = 21; - break; - } - //Check for Windows1251 - else if (_getIsWindows1251Character(_encodedText[i].codeUnitAt(0))) { - _eciAssignmentNumber = 22; - break; - } - //Check for Windows1252 - else if (_getIsWindows1252Character(_encodedText[i].codeUnitAt(0))) { - _eciAssignmentNumber = 23; - break; - } - //Check for Windows1256 - else if (_getIsWindows1256Character(_encodedText[i].codeUnitAt(0))) { - _eciAssignmentNumber = 24; - break; - } - } - } - } - - /// Method to initialize the version and the error correction level - /// - /// This is a very large method. This method could not be - /// refactored to a smaller methods, since it has single if else condition and - /// it sets the data capacity and error correction level based on the input - /// mode - void _initializeVersionAndECR() { - if (!_isUserMentionedVersion || _codeVersion == QRCodeVersion.auto) { - List dataCapacityOfVersions; - if (_isUserMentionedErrorCorrectionLevel) { - switch (_inputMode) { - case QRInputMode.numeric: - { - dataCapacityOfVersions = _getDataCapacityForNumericMode(); - break; - } - - case QRInputMode.alphaNumeric: - { - dataCapacityOfVersions = _getDataCapacityForAlphaNumericMode(); - break; - } - - case QRInputMode.binary: - { - dataCapacityOfVersions = _getDataCapacityForBinaryMode(); - break; - } - } - } else { - _errorCorrectionLevel = ErrorCorrectionLevel.low; - dataCapacityOfVersions = _getDataCapacityVersionForInputMode(); - } - int i; - for (i = 0; i < dataCapacityOfVersions.length; i++) { - if (dataCapacityOfVersions[i] > _encodedText.length) { - break; - } - } - - final int calculatedVersion = i + 1; - - if (calculatedVersion > 40) { - return; - } else { - _codeVersion = _getVersionBasedOnNumber(calculatedVersion); - } - } else if (_isUserMentionedVersion) { - if (_isUserMentionedErrorCorrectionLevel) { - int capacity = 0; - if (_inputMode == QRInputMode.alphaNumeric) { - capacity = _QRCodeValue._getAlphaNumericDataCapacity( - _errorCorrectionLevel, _codeVersion); - } else if (_inputMode == QRInputMode.numeric) { - capacity = _QRCodeValue._getNumericDataCapacity( - _errorCorrectionLevel, _codeVersion); - } else if (_inputMode == QRInputMode.binary) { - capacity = _QRCodeValue._getBinaryDataCapacity( - _errorCorrectionLevel, _codeVersion); - } - if (capacity < _encodedText.length) { - throw 'The input value length is greater than version capacity'; - } - } else { - int capacityLow = 0, - capacityMedium = 0, - capacityQuartile = 0, - capacityHigh = 0; - if (_inputMode == QRInputMode.alphaNumeric) { - capacityLow = _QRCodeValue._getAlphaNumericDataCapacity( - ErrorCorrectionLevel.low, _codeVersion); - capacityMedium = _QRCodeValue._getAlphaNumericDataCapacity( - ErrorCorrectionLevel.medium, _codeVersion); - capacityQuartile = _QRCodeValue._getAlphaNumericDataCapacity( - ErrorCorrectionLevel.quartile, _codeVersion); - capacityHigh = _QRCodeValue._getAlphaNumericDataCapacity( - ErrorCorrectionLevel.high, _codeVersion); - } else if (_inputMode == QRInputMode.numeric) { - capacityLow = _QRCodeValue._getNumericDataCapacity( - ErrorCorrectionLevel.low, _codeVersion); - capacityMedium = _QRCodeValue._getNumericDataCapacity( - ErrorCorrectionLevel.medium, _codeVersion); - capacityQuartile = _QRCodeValue._getNumericDataCapacity( - ErrorCorrectionLevel.quartile, _codeVersion); - capacityHigh = _QRCodeValue._getNumericDataCapacity( - ErrorCorrectionLevel.high, _codeVersion); - } else if (_inputMode == QRInputMode.binary) { - capacityLow = _QRCodeValue._getBinaryDataCapacity( - ErrorCorrectionLevel.low, _codeVersion); - capacityMedium = _QRCodeValue._getBinaryDataCapacity( - ErrorCorrectionLevel.medium, _codeVersion); - capacityQuartile = _QRCodeValue._getBinaryDataCapacity( - ErrorCorrectionLevel.quartile, _codeVersion); - capacityHigh = _QRCodeValue._getBinaryDataCapacity( - ErrorCorrectionLevel.high, _codeVersion); - } - - if (capacityHigh > _encodedText.length) { - _errorCorrectionLevel = ErrorCorrectionLevel.high; - } else if (capacityQuartile > _encodedText.length) { - _errorCorrectionLevel = ErrorCorrectionLevel.quartile; - } else if (capacityMedium > _encodedText.length) { - _errorCorrectionLevel = ErrorCorrectionLevel.medium; - } else if (capacityLow > _encodedText.length) { - _errorCorrectionLevel = ErrorCorrectionLevel.low; - } else { - throw 'The input value length is greater than version capacity'; - } - } - } - } - - /// Returns the data capacity for numeric mode - List _getDataCapacityForNumericMode() { - List dataCapacityOfVersions; - switch (_errorCorrectionLevel) { - case ErrorCorrectionLevel.low: - dataCapacityOfVersions = _QRCodeValue._numericDataCapacityLow; - break; - case ErrorCorrectionLevel.medium: - dataCapacityOfVersions = _QRCodeValue._numericDataCapacityMedium; - break; - case ErrorCorrectionLevel.quartile: - dataCapacityOfVersions = _QRCodeValue._numericDataCapacityQuartile; - break; - case ErrorCorrectionLevel.high: - dataCapacityOfVersions = _QRCodeValue._numericDataCapacityHigh; - break; - } - - return dataCapacityOfVersions; - } - - /// Returns the data capacity for alpha numeric mode - List _getDataCapacityForAlphaNumericMode() { - List dataCapacityOfVersions; - switch (_errorCorrectionLevel) { - case ErrorCorrectionLevel.low: - dataCapacityOfVersions = _QRCodeValue._alphaNumericDataCapacityLow; - break; - case ErrorCorrectionLevel.medium: - dataCapacityOfVersions = _QRCodeValue._alphaNumericDataCapacityMedium; - break; - case ErrorCorrectionLevel.quartile: - dataCapacityOfVersions = _QRCodeValue._alphaNumericDataCapacityQuartile; - break; - case ErrorCorrectionLevel.high: - dataCapacityOfVersions = _QRCodeValue._alphaNumericDataCapacityHigh; - break; - } - - return dataCapacityOfVersions; - } - - /// Returns the data capacity for binary mode - List _getDataCapacityForBinaryMode() { - List dataCapacityOfVersions; - switch (_errorCorrectionLevel) { - case ErrorCorrectionLevel.low: - dataCapacityOfVersions = _QRCodeValue._binaryDataCapacityLow; - break; - case ErrorCorrectionLevel.medium: - dataCapacityOfVersions = _QRCodeValue._binaryDataCapacityMedium; - break; - case ErrorCorrectionLevel.quartile: - dataCapacityOfVersions = _QRCodeValue._binaryDataCapacityQuartile; - break; - case ErrorCorrectionLevel.high: - dataCapacityOfVersions = _QRCodeValue._binaryDataCapacityHigh; - break; - } - - return dataCapacityOfVersions; - } - - /// Returns the data capacity version for input - List _getDataCapacityVersionForInputMode() { - List dataCapacityOfVersions; - switch (_inputMode) { - case QRInputMode.numeric: - { - dataCapacityOfVersions = _QRCodeValue._numericDataCapacityLow; - break; - } - - case QRInputMode.alphaNumeric: - { - dataCapacityOfVersions = _QRCodeValue._alphaNumericDataCapacityLow; - break; - } - - case QRInputMode.binary: - { - dataCapacityOfVersions = _QRCodeValue._binaryDataCapacityLow; - break; - } - } - - return dataCapacityOfVersions; - } - - /// Method to draw the format information - void _drawFormatInformation() { - final List formatInformation = _qrCodeValues._formatInformation; - int count = 0; - for (int i = 0; i < 7; i++) { - if (i == 6) { - _moduleValues[i + 1][8].isBlack = - formatInformation[count] == 1 ? true : false; - } else { - _moduleValues[i][8].isBlack = - formatInformation[count] == 1 ? true : false; - } - - _moduleValues[8][_noOfModules - i - 1].isBlack = - formatInformation[count++] == 1 ? true : false; - } - - count = 14; - - for (int i = 0; i < 7; i++) { - //Draw format information from 0 to 6 - if (i == 6) { - _moduleValues[8][i + 1].isBlack = - formatInformation[count] == 1 ? true : false; - } else { - _moduleValues[8][i].isBlack = - formatInformation[count] == 1 ? true : false; - } - - _moduleValues[_noOfModules - i - 1][8].isBlack = - formatInformation[count--] == 1 ? true : false; - } - - //Draw format information 7 - _moduleValues[8][8].isBlack = formatInformation[7] == 1 ? true : false; - _moduleValues[8][_noOfModules - 8].isBlack = - formatInformation[7] == 1 ? true : false; - } - - /// Method used for data allocation and masking - /// - /// This is deliberately a very large method. This method could be - /// refactored to a smaller methods, but it degrades the performance.Since it - /// uses single for loop for calculating the data allocation values - void _dataAllocationAndMasking(List data) { - _dataAllocationValues = List>.generate( - _noOfModules, - (int i) => List<_ModuleValue>.generate( - _noOfModules, (int j) => _ModuleValue())); - - int point = 0; - - for (int i = _noOfModules - 1; i >= 0; i -= 2) { - for (int j = _noOfModules - 1; j >= 0; j--) { - if (!(_moduleValues[i][j].isFilled && - _moduleValues[i - 1][j].isFilled)) { - if (!_moduleValues[i][j].isFilled) { - if (point + 1 < data.length) { - _dataAllocationValues[i][j].isBlack = data[point++]; - } - - if ((i + j) % 3 == 0) { - if (_dataAllocationValues[i][j].isBlack) { - _dataAllocationValues[i][j].isBlack = true; - } else { - _dataAllocationValues[i][j].isBlack = false; - } - } else { - if (_dataAllocationValues[i][j].isBlack) { - _dataAllocationValues[i][j].isBlack = false; - } else { - _dataAllocationValues[i][j].isBlack = true; - } - } - - _dataAllocationValues[i][j].isFilled = true; - } - - if (!_moduleValues[i - 1][j].isFilled) { - if (point + 1 < data.length) { - _dataAllocationValues[i - 1][j].isBlack = data[point++]; - } - - if ((i - 1 + j) % 3 == 0) { - if (_dataAllocationValues[i - 1][j].isBlack) { - _dataAllocationValues[i - 1][j].isBlack = true; - } else { - _dataAllocationValues[i - 1][j].isBlack = false; - } - } else { - if (_dataAllocationValues[i - 1][j].isBlack) { - _dataAllocationValues[i - 1][j].isBlack = false; - } else { - _dataAllocationValues[i - 1][j].isBlack = true; - } - } - _dataAllocationValues[i - 1][j].isFilled = true; - } - } - } - - i -= 2; - if (i == 6) { - i--; - } - - for (int j = 0; j < _noOfModules; j++) { - if (!(_moduleValues[i][j].isFilled && - _moduleValues[i - 1][j].isFilled)) { - if (!_moduleValues[i][j].isFilled) { - if (point + 1 < data.length) { - _dataAllocationValues[i][j].isBlack = data[point++]; - } - - if ((i + j) % 3 == 0) { - if (_dataAllocationValues[i][j].isBlack) { - _dataAllocationValues[i][j].isBlack = true; - } else { - _dataAllocationValues[i][j].isBlack = false; - } - } else { - if (_dataAllocationValues[i][j].isBlack) { - _dataAllocationValues[i][j].isBlack = false; - } else { - _dataAllocationValues[i][j].isBlack = true; - } - } - _dataAllocationValues[i][j].isFilled = true; - } - - if (!_moduleValues[i - 1][j].isFilled) { - if (point + 1 < data.length) { - _dataAllocationValues[i - 1][j].isBlack = data[point++]; - } - - if ((i - 1 + j) % 3 == 0) { - if (_dataAllocationValues[i - 1][j].isBlack) { - _dataAllocationValues[i - 1][j].isBlack = true; - } else { - _dataAllocationValues[i - 1][j].isBlack = false; - } - } else { - if (_dataAllocationValues[i - 1][j].isBlack) { - _dataAllocationValues[i - 1][j].isBlack = false; - } else { - _dataAllocationValues[i - 1][j].isBlack = true; - } - } - _dataAllocationValues[i - 1][j].isFilled = true; - } - } - } - } - for (int i = 0; i < _noOfModules; i++) { - for (int j = 0; j < _noOfModules; j++) { - if (!_moduleValues[i][j].isFilled) { - final bool flag = _dataAllocationValues[i][j].isBlack; - if (flag) { - _dataAllocationValues[i][j].isBlack = false; - } else { - _dataAllocationValues[i][j].isBlack = true; - } - } - } - } - } - - /// Method to draw the alignment pattern - void _drawAlignmentPattern(int x, int y) { - int i = x - 2, j = y - 2; - for (; i < x + 3; i++, j++) { - _moduleValues[i][y - 2].isBlack = true; - _moduleValues[i][y - 2].isFilled = true; - - _moduleValues[i][y + 2].isBlack = true; - _moduleValues[i][y + 2].isFilled = true; - - _moduleValues[x - 2][j].isBlack = true; - _moduleValues[x - 2][j].isFilled = true; - - _moduleValues[x + 2][j].isBlack = true; - _moduleValues[x + 2][j].isFilled = true; - } - - i = x - 1; - j = y - 1; - for (; i < x + 2; i++, j++) { - _moduleValues[i][y - 1].isBlack = false; - _moduleValues[i][y - 1].isFilled = true; - - _moduleValues[i][y + 1].isBlack = false; - _moduleValues[i][y + 1].isFilled = true; - - _moduleValues[x - 1][j].isBlack = false; - _moduleValues[x - 1][j].isFilled = true; - - _moduleValues[x + 1][j].isBlack = false; - _moduleValues[x + 1][j].isFilled = true; - } - - _moduleValues[x][y].isBlack = true; - _moduleValues[x][y].isFilled = true; - } - - /// Method to draw the timing pattern - void _drawTimingPattern() { - for (int i = 8; i < _noOfModules - 8; i += 2) { - _moduleValues[i][6].isBlack = true; - _moduleValues[i][6].isFilled = true; - - _moduleValues[i + 1][6].isBlack = false; - _moduleValues[i + 1][6].isFilled = true; - - _moduleValues[6][i].isBlack = true; - _moduleValues[6][i].isFilled = true; - - _moduleValues[6][i + 1].isBlack = false; - _moduleValues[6][i + 1].isFilled = true; - } - - _moduleValues[_noOfModules - 8][8].isBlack = true; - _moduleValues[_noOfModules - 8][8].isFilled = true; - } - - /// Method to draw the PDP - /// - /// This is a very large method. This method could be - /// refactored to a smaller methods, but it degrades the performance.Since it - /// uses multiple for loop for allocating the module values values - void _drawPDP(int x, int y) { - int i = x; - int j = y; - for (; i < x + 7; i++, j++) { - _moduleValues[i][y].isBlack = true; - _moduleValues[i][y].isFilled = true; - _moduleValues[i][y].isPDP = true; - - _moduleValues[i][y + 6].isBlack = true; - _moduleValues[i][y + 6].isFilled = true; - _moduleValues[i][y + 6].isPDP = true; - - if (y + 7 < _noOfModules) { - _moduleValues[i][y + 7].isBlack = false; - _moduleValues[i][y + 7].isFilled = true; - _moduleValues[i][y + 7].isPDP = true; - } else if (y - 1 >= 0) { - _moduleValues[i][y - 1].isBlack = false; - _moduleValues[i][y - 1].isFilled = true; - _moduleValues[i][y - 1].isPDP = true; - } - - _moduleValues[x][j].isBlack = true; - _moduleValues[x][j].isFilled = true; - _moduleValues[x][j].isPDP = true; - - _moduleValues[x + 6][j].isBlack = true; - _moduleValues[x + 6][j].isFilled = true; - _moduleValues[x + 6][j].isPDP = true; - - if (x + 7 < _noOfModules) { - _moduleValues[x + 7][j].isBlack = false; - _moduleValues[x + 7][j].isFilled = true; - _moduleValues[x + 7][j].isPDP = true; - } else if (x - 1 >= 0) { - _moduleValues[x - 1][j].isBlack = false; - _moduleValues[x - 1][j].isFilled = true; - _moduleValues[x - 1][j].isPDP = true; - } - } - - if (x + 7 < _noOfModules && y + 7 < _noOfModules) { - _moduleValues[x + 7][y + 7].isBlack = false; - _moduleValues[x + 7][y + 7].isFilled = true; - _moduleValues[x + 7][y + 7].isPDP = true; - } else if (x + 7 < _noOfModules && y + 7 >= _noOfModules) { - _moduleValues[x + 7][y - 1].isBlack = false; - _moduleValues[x + 7][y - 1].isFilled = true; - _moduleValues[x + 7][y - 1].isPDP = true; - } else if (x + 7 >= _noOfModules && y + 7 < _noOfModules) { - _moduleValues[x - 1][y + 7].isBlack = false; - _moduleValues[x - 1][y + 7].isFilled = true; - _moduleValues[x - 1][y + 7].isPDP = true; - } - - x++; - y++; - i = x; - j = y; - for (; i < x + 5; i++, j++) { - _moduleValues[i][y].isBlack = false; - _moduleValues[i][y].isFilled = true; - _moduleValues[i][y].isPDP = true; - - _moduleValues[i][y + 4].isBlack = false; - _moduleValues[i][y + 4].isFilled = true; - _moduleValues[i][y + 4].isPDP = true; - - _moduleValues[x][j].isBlack = false; - _moduleValues[x][j].isFilled = true; - _moduleValues[x][j].isPDP = true; - - _moduleValues[x + 4][j].isBlack = false; - _moduleValues[x + 4][j].isFilled = true; - _moduleValues[x + 4][j].isPDP = true; - } - - x++; - y++; - i = x; - j = y; - for (; i < x + 3; i++, j++) { - _moduleValues[i][y].isBlack = true; - _moduleValues[i][y].isFilled = true; - _moduleValues[i][y].isPDP = true; - - _moduleValues[i][y + 2].isBlack = true; - _moduleValues[i][y + 2].isFilled = true; - _moduleValues[i][y + 2].isPDP = true; - - _moduleValues[x][j].isBlack = true; - _moduleValues[x][j].isFilled = true; - _moduleValues[x][j].isPDP = true; - - _moduleValues[x + 2][j].isBlack = true; - _moduleValues[x + 2][j].isFilled = true; - _moduleValues[x + 2][j].isPDP = true; - } - - _moduleValues[x + 1][y + 1].isBlack = true; - _moduleValues[x + 1][y + 1].isFilled = true; - _moduleValues[x + 1][y + 1].isPDP = true; - } - - /// Method used to generate the value - void _generateValues() { - _initialize(); - _qrCodeValues = _QRCodeValue( - qrCodeVersion: _codeVersion, - errorCorrectionLevel: _errorCorrectionLevel); - _moduleValues = List>.generate( - _noOfModules, - (int i) => List<_ModuleValue>.generate( - _noOfModules, (int j) => _ModuleValue())); - _drawPDP(0, 0); - _drawPDP(_noOfModules - 7, 0); - _drawPDP(0, _noOfModules - 7); - _drawTimingPattern(); - if (_codeVersion != QRCodeVersion.version01) { - final List alignCoordinates = _getAlignmentPatternCoordinates(); - - for (int i = 0; i < alignCoordinates.length; i++) { - for (int j = 0; j < alignCoordinates.length; j++) { - if (!_moduleValues[alignCoordinates[i]][alignCoordinates[j]].isPDP) { - _drawAlignmentPattern(alignCoordinates[i], alignCoordinates[j]); - } - } - } - } - - _allocateFormatAndVersionInformation(); - _encodeDataCodeWords = _encodeData(); - _dataAllocationAndMasking(_encodeDataCodeWords); - _drawFormatInformation(); - } - - /// Method to encoded the data based on mode - void _encodeDataBasedOnMode() { - switch (_inputMode) { - case QRInputMode.numeric: - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(true); - break; - - case QRInputMode.alphaNumeric: - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(true); - _encodeDataCodeWords.add(false); - break; - - case QRInputMode.binary: - if (_isEci) { - //Add ECI Mode Indicator - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(true); - _encodeDataCodeWords.add(true); - _encodeDataCodeWords.add(true); - - //Add ECI assignment number - final List numberInBool = - _getStringToBoolArray(_eciAssignmentNumber.toString(), 8); - for (int i = 0; i < numberInBool.length; i++) { - _encodeDataCodeWords.add(numberInBool[i]); - } - } - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(true); - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(false); - break; - } - } - - /// Method to get the indicator count for lower version - int _getIndicatorCountForLowerVersion() { - int numberOfBitsInCharacterCountIndicator = 0; - switch (_inputMode) { - case QRInputMode.numeric: - numberOfBitsInCharacterCountIndicator = 10; - break; - - case QRInputMode.alphaNumeric: - numberOfBitsInCharacterCountIndicator = 9; - break; - case QRInputMode.binary: - numberOfBitsInCharacterCountIndicator = 8; - break; - } - - return numberOfBitsInCharacterCountIndicator; - } - - /// method to get the indicator count for higher version - int _getIndicatorCountForHigherVersion() { - int numberOfBitsInCharacterCountIndicator = 0; - switch (_inputMode) { - case QRInputMode.numeric: - numberOfBitsInCharacterCountIndicator = 12; - break; - - case QRInputMode.alphaNumeric: - numberOfBitsInCharacterCountIndicator = 11; - break; - - case QRInputMode.binary: - numberOfBitsInCharacterCountIndicator = 16; - break; - } - - return numberOfBitsInCharacterCountIndicator; - } - - /// Method to get the indicator count - int _getIndicatorCount() { - int numberOfBitsInCharacterCountIndicator = 0; - switch (_inputMode) { - case QRInputMode.numeric: - numberOfBitsInCharacterCountIndicator = 14; - break; - - case QRInputMode.alphaNumeric: - numberOfBitsInCharacterCountIndicator = 13; - break; - - case QRInputMode.binary: - numberOfBitsInCharacterCountIndicator = 16; - break; - } - return numberOfBitsInCharacterCountIndicator; - } - - /// Method to encode the data of numeric mode - void _encodeDataInNumericMode() { - String number = ''; - for (int i = 0; i < _encodedText.length; i++) { - List numberInBool; - number += _encodedText[i]; - - if (i % 3 == 2 && i != 0 || i == _encodedText.length - 1) { - if (number.length == 3) { - numberInBool = _getStringToBoolArray(number, 10); - } else if (number.length == 2) { - numberInBool = _getStringToBoolArray(number, 7); - } else { - numberInBool = _getStringToBoolArray(number, 4); - } - - number = ''; - for (int i = 0; i < numberInBool.length; i++) { - _encodeDataCodeWords.add(numberInBool[i]); - } - } - } - } - - /// Method to encode the alpha numeric data - void _encodeDataForAlphaNumeric() { - String numberInString = ''; - int number = 0; - for (int i = 0; i < _encodedText.length; i++) { - List numberInBool; - numberInString += _encodedText[i]; - - if (i % 2 == 0 && i + 1 != _encodedText.length) { - number = 45 * _qrCodeValues._getAlphaNumericValues(_encodedText[i]); - } - - if (i % 2 == 1 && i != 0) { - number += _qrCodeValues._getAlphaNumericValues(_encodedText[i]); - numberInBool = _getIntToBoolArray(number, 11); - number = 0; - for (int i = 0; i < numberInBool.length; i++) { - _encodeDataCodeWords.add(numberInBool[i]); - } - - numberInString = ''; - } - if (i != 1 && numberInString != null) { - if (i + 1 == _encodedText.length && numberInString.length == 1) { - number = _qrCodeValues._getAlphaNumericValues(_encodedText[i]); - numberInBool = _getIntToBoolArray(number, 6); - number = 0; - for (int i = 0; i < numberInBool.length; i++) { - _encodeDataCodeWords.add(numberInBool[i]); - } - } - } - } - } - - /// method to encode the binary data - void _encodeDataForBinary() { - for (int i = 0; i < _encodedText.length; i++) { - int number = 0; - if ((_encodedText[i].codeUnitAt(0) >= 32 && - _encodedText[i].codeUnitAt(0) <= 126) || - _encodedText[i].codeUnitAt(0) >= 161 && - _encodedText[i].codeUnitAt(0) <= 255 || - _encodedText[i].codeUnitAt(0) == 10 || - _encodedText[i].codeUnitAt(0) == 13) { - number = _encodedText[i].codeUnitAt(0); - } else if (_encodedText[i].codeUnitAt(0) >= 65377 && - _encodedText[i].codeUnitAt(0) <= 65439) { - number = _encodedText[i].codeUnitAt(0) - 65216; - } else if (_encodedText[i].codeUnitAt(0) >= 1025 && - _encodedText[i].codeUnitAt(0) <= 1119) { - number = _encodedText[i].codeUnitAt(0) - 864; - } else if (_encodedText[i].codeUnitAt(0) >= 260 && - _encodedText[i].codeUnitAt(0) <= 382) { - /// European Encoding - } else { - throw 'The provided input value contains non-convertible characters'; - } - - final List numberInBool = _getIntToBoolArray(number, 8); - for (int i = 0; i < numberInBool.length; i++) { - _encodeDataCodeWords.add(numberInBool[i]); - } - } - } - - /// Method to encode the code words - /// - /// This is a very large method. This method could be - /// refactored to a smaller methods, but it degrades the performance.Since it - /// uses multiple for loop for encoding the code words - void _encodeCodeWords() { - for (int i = 0; i < 4; i++) { - if (_encodeDataCodeWords.length / 8 == _qrCodeValues._noOfDataCodeWord) { - break; - } else { - _encodeDataCodeWords.add(false); - } - } - - for (;;) { - if (_encodeDataCodeWords.length % 8 == 0) { - break; - } else { - _encodeDataCodeWords.add(false); - } - } - - for (;;) { - if (_encodeDataCodeWords.length / 8 == _qrCodeValues._noOfDataCodeWord) { - break; - } else { - _encodeDataCodeWords.add(true); - _encodeDataCodeWords.add(true); - _encodeDataCodeWords.add(true); - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(true); - _encodeDataCodeWords.add(true); - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(false); - } - if (_encodeDataCodeWords.length / 8 == _qrCodeValues._noOfDataCodeWord) { - break; - } else { - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(true); - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(false); - _encodeDataCodeWords.add(true); - } - } - - _dataBits = _qrCodeValues._noOfDataCodeWord; - _blocks = _qrCodeValues._noOfErrorCorrectionBlocks; - - int totalBlockSize = _blocks[0]; - if (_blocks.length == 6) { - totalBlockSize = _blocks[0] + _blocks[3]; - } - final List> ds1 = - List>.generate(totalBlockSize, (int i) => []); - - List testEncodeData = _encodeDataCodeWords; - if (_blocks.length == 6) { - final int dataCodeWordLength = _blocks[0] * _blocks[2] * 8; - testEncodeData = []; - for (int i = 0; i < dataCodeWordLength; i++) { - testEncodeData.add(_encodeDataCodeWords[i]); - } - } - - List> dsOne = List>.generate(_blocks[0], - (int i) => List(testEncodeData.length ~/ 8 ~/ _blocks[0])); - dsOne = _getBlocks(testEncodeData, _blocks[0]); - - for (int i = 0; i < _blocks[0]; i++) { - 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++) { - testEncodeData.add(_encodeDataCodeWords[i]); - } - - List> dsTwo = List>.generate(_blocks[0], - (int i) => List(testEncodeData.length ~/ 8 ~/ _blocks[3])); - 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]); - } - } - - _encodeDataCodeWords = []; - for (int i = 0; i < 125; i++) { - for (int k = 0; k < totalBlockSize; k++) { - for (int j = 0; j < 8; j++) { - if (i < ds1[k].length) { - _encodeDataCodeWords.add(ds1[k][i][j] == '1' ? true : false); - } - } - } - } - - _calculateErrorCorrectingCodeWord(totalBlockSize, ds1); - } - - /// Method to encode the data - List _encodeData() { - _encodeDataCodeWords = []; - _encodeDataBasedOnMode(); - - int numberOfBitsInCharacterCountIndicator = 0; - final int currentVersion = _getVersionNumber(_codeVersion); - if (currentVersion < 10) { - numberOfBitsInCharacterCountIndicator = - _getIndicatorCountForLowerVersion(); - } else if (currentVersion < 27) { - numberOfBitsInCharacterCountIndicator = - _getIndicatorCountForHigherVersion(); - } else { - numberOfBitsInCharacterCountIndicator = _getIndicatorCount(); - } - - final List numberOfBitsInCharacterCountIndicatorInBool = - _getIntToBoolArray( - _encodedText.length, numberOfBitsInCharacterCountIndicator); - - for (int i = 0; i < numberOfBitsInCharacterCountIndicator; i++) { - _encodeDataCodeWords.add(numberOfBitsInCharacterCountIndicatorInBool[i]); - } - - switch (_inputMode) { - case QRInputMode.numeric: - _encodeDataInNumericMode(); - break; - case QRInputMode.alphaNumeric: - _encodeDataForAlphaNumeric(); - break; - case QRInputMode.binary: - _encodeDataForBinary(); - break; - } - - _encodeCodeWords(); - return _encodeDataCodeWords; - } - - /// Method to calculate the error correcting code word - void _calculateErrorCorrectingCodeWord( - int totalBlockSize, List> ds1) { - final _ErrorCorrectionCodeWords errorCorrectionCodeWord = - _ErrorCorrectionCodeWords( - codeVersion: _codeVersion, correctionLevel: _errorCorrectionLevel); - - _dataBits = _qrCodeValues._noOfDataCodeWord; - final int eccw = _qrCodeValues._noOfErrorCorrectionCodeWord; - _blocks = _qrCodeValues._noOfErrorCorrectionBlocks; - - if (_blocks.length == 6) { - errorCorrectionCodeWord._dataBits = - (_dataBits - _blocks[3] * _blocks[5]) ~/ _blocks[0]; - } else { - errorCorrectionCodeWord._dataBits = _dataBits ~/ _blocks[0]; - } - - errorCorrectionCodeWord._eccw = eccw ~/ totalBlockSize; - - final List> polynomial = - List>.generate(totalBlockSize, (int i) => []); - int count = 0; - - for (int i = 0; i < _blocks[0]; i++) { - errorCorrectionCodeWord._dataCodeWords = ds1[count]; - polynomial[count++] = errorCorrectionCodeWord._getERCW(); - } - if (_blocks.length == 6) { - errorCorrectionCodeWord._dataBits = - (_dataBits - _blocks[0] * _blocks[2]) ~/ _blocks[3]; - - for (int i = 0; i < _blocks[3]; i++) { - errorCorrectionCodeWord._dataCodeWords = ds1[count]; - polynomial[count++] = errorCorrectionCodeWord._getERCW(); - } - } - - if (_blocks.length != 6) { - for (int i = 0; i < polynomial[0].length; i++) { - for (int k = 0; k < _blocks[0]; k++) { - for (int j = 0; j < 8; j++) { - if (i < polynomial[k].length) { - _encodeDataCodeWords - .add(polynomial[k][i][j] == '1' ? true : false); - } - } - } - } - } else { - for (int i = 0; i < polynomial[0].length; i++) { - for (int k = 0; k < totalBlockSize; k++) { - for (int j = 0; j < 8; j++) { - if (i < polynomial[k].length) { - _encodeDataCodeWords - .add(polynomial[k][i][j] == '1' ? true : false); - } - } - } - } - } - } - - @override - void _renderBarcode( - Canvas canvas, - Size size, - Offset offset, - String value, - Color foregroundColor, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - bool showValue) { - _encodedText = value; - _generateValues(); - - int x = 0; - final int w = _noOfModules; - final int h = _noOfModules; - - final Paint paint = _getBarPaint(foregroundColor); - - final double minSize = size.width >= size.height ? size.height : size.width; - int dimension = minSize ~/ _noOfModules; - if (module != null) { - dimension = module; - } - final double actualSize = (_noOfModules * dimension).toDouble(); - final int xPosition = (offset.dx + (size.width - actualSize) / 2).toInt(); - int yPosition = (offset.dy + (size.height - actualSize) / 2).toInt(); - - for (int i = 0; i < w; i++) { - x = xPosition; - for (int j = 0; j < h; j++) { - if (_moduleValues[i][j].isBlack) { - paint.color = foregroundColor; - } else { - paint.color = Colors.transparent; - } - - if (_dataAllocationValues != null && - _dataAllocationValues[j][i].isFilled) { - if (_dataAllocationValues[j][i].isBlack) { - paint.color = foregroundColor; - } - } - - final Rect rect = Rect.fromLTRB(x.toDouble(), yPosition.toDouble(), - (x + dimension).toDouble(), (yPosition + dimension).toDouble()); - canvas.drawRect(rect, paint); - - x = (x + dimension).toInt(); - } - - yPosition = (yPosition + dimension).toInt(); - } - - if (showValue) { - final Offset textOffset = Offset(offset.dx, yPosition.toDouble()); - _drawText( - 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]) { - 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); - double x; - double y; - switch (textAlign) { - case TextAlign.justify: - case TextAlign.center: - { - x = offset.dx + (size.width / 2 - textPainter.width / 2); - y = offset.dy + textSpacing; - } - break; - case TextAlign.left: - case TextAlign.start: - { - x = offset.dx; - y = offset.dy + textSpacing; - } - break; - case TextAlign.right: - case TextAlign.end: - { - x = offset.dx + (size.width - textPainter.width); - y = offset.dy + textSpacing; - } - break; - } - textPainter.paint(canvas, Offset(x, y)); - } } 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 f40b7c123..3fd6a47c9 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 @@ -1,5 +1,3 @@ -part of barcodes; - /// Represents the input character symbol. enum CodeType { ///CodeType.uncodable represents the input symbol is not codable. diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/utils/helper.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/utils/helper.dart index 6180b1268..e8cc7a40f 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/utils/helper.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/utils/helper.dart @@ -1,7 +1,10 @@ -part of barcodes; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../utils/enum.dart'; -// Measure the text and return the text size -Size _measureText(String textValue, TextStyle textStyle) { +/// Measure the text and return the text size +Size measureText(String textValue, TextStyle textStyle) { Size size; final TextPainter textPainter = TextPainter( textAlign: TextAlign.center, @@ -13,7 +16,8 @@ Size _measureText(String textValue, TextStyle textStyle) { return size; } -int _getVersionNumber(QRCodeVersion qrCodeVersion) { +/// Returns the code version number +int getVersionNumber(QRCodeVersion qrCodeVersion) { switch (qrCodeVersion) { case QRCodeVersion.version01: return 1; @@ -102,7 +106,8 @@ int _getVersionNumber(QRCodeVersion qrCodeVersion) { return 0; } -QRCodeVersion _getVersionBasedOnNumber(int qrCodeVersion) { +/// Returns the version based on number +QRCodeVersion getVersionBasedOnNumber(int qrCodeVersion) { switch (qrCodeVersion) { case 1: return QRCodeVersion.version01; @@ -191,7 +196,8 @@ QRCodeVersion _getVersionBasedOnNumber(int qrCodeVersion) { return QRCodeVersion.auto; } -int _getDataMatrixSize(DataMatrixSize dataMatrixSize) { +/// Returns the data matrix size +int getDataMatrixSize(DataMatrixSize dataMatrixSize) { switch (dataMatrixSize) { case DataMatrixSize.size10x10: return 1; diff --git a/packages/syncfusion_flutter_calendar/CHANGELOG.md b/packages/syncfusion_flutter_calendar/CHANGELOG.md index dc78037ae..653232cb1 100644 --- a/packages/syncfusion_flutter_calendar/CHANGELOG.md +++ b/packages/syncfusion_flutter_calendar/CHANGELOG.md @@ -1,3 +1,26 @@ +## [13.4.30] +**Features** +* The custom builder support is provided for the time region and the appointment views in the calendar. +* Provided the interaction support for the resource header. +* Support is provided to the right end padding for the cell touch region when the cell has an appointment in the calendar. + +**Enhancements** +* The animation for view switching, selection ripple effect, and header picker pop-up animation is improved. + + +## [18.3.50] - 11/17/2020 +**Bug fixes** +* Now, the time of the `displayDate` of `CalendarController` is working properly with the `timeInterval` changes. + + +## [18.3.48] - 11/11/2020 +**Bug fixes** +* Now, the `SfCalendar` time zone support has been enhanced to IANA time zone support. + + +## [18.3.38] - 10/07/2020 +No changes. + ## [18.3.35] - 10/01/2020 **Bug fixes** * Now, the appointment will render on the correct timeslot, when the local set as French, Canada, and in `Eastern Standard Time`. @@ -17,6 +40,7 @@ * The `timeIntervalHeight` property will not work for timeline views, use the `timeIntervalWidth` property instead for the timeline views alone. * The default value for the `timeIntervalWidth` is changed from 40 to 60. * The appointment UI width is reduced in the day, week, and workweek views. +* Now, the `view` property from the `SfCalendar` does not work dynamically. To switch the view dynamically, use the same property from the `CalendarController`. ## [18.2.59] - 09/23/2020 No changes. diff --git a/packages/syncfusion_flutter_calendar/README.md b/packages/syncfusion_flutter_calendar/README.md index 26a62e16b..9b035de98 100644 --- a/packages/syncfusion_flutter_calendar/README.md +++ b/packages/syncfusion_flutter_calendar/README.md @@ -29,11 +29,11 @@ The Syncfusion Flutter Calendar widget has built-in configurable views that prov * **Multiple calendar views** - A wide range of built-in view modes are available: day, week, workweek, month, schedule, timeline day, timeline week, timeline workweek. The control allows you to conveniently customize every view with unique, view-specific options. -![multiple_calenda_views](https://cdn.syncfusion.com/content/images/FTControl/Flutter/flutter-calendar-schedule.png) +![multiple_calenda_views](https://cdn.syncfusion.com/content/images/FTControl/Calendar/flutter-calendar-views.png) * **Appointments** - Appointments contain information on events scheduled at specific times. In addition to default appointments, users can use their own collections to connect a business entity to an appointment by mapping their fields, such as start time, end time, subject, notes, and recurrence. -![appointments](https://cdn.syncfusion.com/content/images/FTControl/Flutter/flutter-calendar-schedule-view-appointments.png) +![appointments](https://cdn.syncfusion.com/content/images/FTControl/Calendar/flutter-calendar-appointments.png) * **Recurring appointments** - Easily configure recurring events to be repeated on a daily, weekly, monthly, or yearly basis with optimized recurrence options. You can also skip or change the occurrence of a recurring appointment. @@ -45,9 +45,11 @@ The Syncfusion Flutter Calendar widget has built-in configurable views that prov * **Resource view** - Display resources as a discrete view integrated with the calendar, to display appointments of each resource in a timeline view to enhance viewability. You can customize everything from the display name, resource panel size, background color and image of the resource view. +![resource_view](https://cdn.syncfusion.com/content/images/FTControl/Calendar/flutter-calendar-resource-view.png) + * **Schedule view** - Show a list of scheduled appointments grouped by week, between set minimum and maximum dates, with the schedule view. You can customize everything from the date and time formats to the styling of each header. -![schedule_view](https://cdn.syncfusion.com/content/images/FTControl/Flutter/flutter-calendar-schedule-view-appointments-web.png) +![schedule_view](https://cdn.syncfusion.com/content/images/FTControl/Calendar/flutter-calendar-schedule-view.png) * **Special time regions** - Disable interactions and selections for specific time ranges. This is useful when you want to block user interaction during holidays or another special events and to highlight those time slots. @@ -63,8 +65,12 @@ The Syncfusion Flutter Calendar widget has built-in configurable views that prov * **Blackout dates** - Disable any date in a month and timeline month view of a calendar to make it inactive. Easily prevent the selection of weekends and holidays by disabling them. +![blackout_dates](https://cdn.syncfusion.com/content/images/FTControl/Calendar/flutter-calendar-blackoutdates.png) + * **Customize leading and trailing dates** - Hide the days of the next month and previous month in calendar to enhance the appearance. +![leading_trailing_dates](https://cdn.syncfusion.com/content/images/FTControl/Calendar/flutter-calendar-leading-trailing-dates.png) + * **Custom start and end hours** - Display the event calendar timeslot views with specific time durations by hiding the unwanted hours. * **Month agenda view** - Display appointments in a list as shown in the following month view by clicking on a day. @@ -73,7 +79,11 @@ The Syncfusion Flutter Calendar widget has built-in configurable views that prov * **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. -* **Builder** - Allows to design and set own custom view to the month cells and month header of schedule view of the calendar. +![quick_view_navigation](https://cdn.syncfusion.com/content/images/FTControl/Calendar/flutter-calendar-quickview-navigation.png) + +* **Builders** - Allows you to design and set your own custom view to the month cells, month header of schedule view, special time regions, and appointments view of the calendar. + +![bulders_in_calendar](https://cdn.syncfusion.com/content/images/FTControl/Calendar/builders.png) * **Appearance customization or Theming** - Provide a uniform and consistent look to the Calendar’s appearance and format. Theming support to provide a consistent look to the calendar. diff --git a/packages/syncfusion_flutter_calendar/lib/calendar.dart b/packages/syncfusion_flutter_calendar/lib/calendar.dart index 44e98ce8c..9cbcb6497 100644 --- a/packages/syncfusion_flutter_calendar/lib/calendar.dart +++ b/packages/syncfusion_flutter_calendar/lib/calendar.dart @@ -46,6 +46,8 @@ part 'src/calendar/views/timeline_view.dart'; part 'src/calendar/views/selection_view.dart'; part 'src/calendar/views/time_ruler_view.dart'; part 'src/calendar/views/schedule_view.dart'; +part 'src/calendar/views/custom_calendar_button.dart'; +part 'src/calendar/views/multi_child_container.dart'; part './src/calendar/settings/time_slot_view_settings.dart'; part './src/calendar/settings/month_view_settings.dart'; 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 b958368c4..cdb79a4e4 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 @@ -37,10 +37,9 @@ DateTime _getMaxAppointmentDate( DateTime maxDate, DateTime displayDate, ScheduleViewSettings scheduleViewSettings, - double viewWidth) { + bool useMobilePlatformUI) { /// return default max date when [hideEmptyAgendaDays] as false - if (!(scheduleViewSettings.hideEmptyScheduleWeek || - (kIsWeb && viewWidth > _kMobileViewWidth))) { + if (!scheduleViewSettings.hideEmptyScheduleWeek && useMobilePlatformUI) { return maxDate; } @@ -142,10 +141,9 @@ DateTime _getMinAppointmentDate( DateTime minDate, DateTime displayDate, ScheduleViewSettings scheduleViewSettings, - double viewWidth) { + bool useMobilePlatformUI) { /// return default min date when [hideEmptyAgendaDays] as false - if (!(scheduleViewSettings.hideEmptyScheduleWeek || - (kIsWeb && viewWidth > _kMobileViewWidth))) { + if (!scheduleViewSettings.hideEmptyScheduleWeek && useMobilePlatformUI) { return minDate; } @@ -671,7 +669,7 @@ Location _timeZoneInfoToOlsonTimeZone(String windowsTimeZoneId) { final String timeZone = olsonWindowsTimes[windowsTimeZoneId]; return getLocation(timeZone); } else { - return null; + return getLocation(windowsTimeZoneId); } } @@ -916,7 +914,7 @@ int _orderAppointmentsAscending(bool value, bool value1) { } void _setAppointmentPositionAndMaxPosition( - Object parent, + List<_AppointmentView> appointmentCollection, SfCalendar calendar, CalendarView view, List visibleAppointments, @@ -962,14 +960,8 @@ void _setAppointmentPositionAndMaxPosition( } List<_AppointmentView> intersectingApps; - _AppointmentView currentAppView; - if (parent is _AppointmentPainter) { - currentAppView = - _getAppointmentView(currentAppointment, parent, resourceIndex); - } else { - final _SfCalendarState state = parent; - currentAppView = state._getAppointmentView(currentAppointment); - } + final _AppointmentView currentAppView = _getAppointmentView( + currentAppointment, appointmentCollection, resourceIndex); for (int position = 0; position < maxColsCount; position++) { bool isIntersecting = false; @@ -1088,21 +1080,49 @@ DateTime _convertTimeToAppointmentTimeZone( //// Convert the date to appointment time zone if (appTimeZoneId == 'Dateline Standard Time') { convertedDate = subtractDuration(date.toUtc(), const Duration(hours: 12)); + //// Above mentioned converted date hold the date value which is equal to original date, but the time zone value changed. + //// E.g., Nov 3- 9.00 AM IST equal to Nov 2- 10.30 PM EST + //// So convert the Appointment time zone date to current time zone date. + convertedDate = DateTime( + date.year - (convertedDate.year - date.year), + date.month - (convertedDate.month - date.month), + date.day - (convertedDate.day - date.day), + date.hour - (convertedDate.hour - date.hour), + date.minute - (convertedDate.minute - date.minute), + date.second); } else { - convertedDate = - TZDateTime.from(date, _timeZoneInfoToOlsonTimeZone(appTimeZoneId)); + /// Create the specified date on appointment time zone. + /// Eg., Appointment Time zone as Eastern time zone(-5.00) and it date is + /// Nov 1 10AM, create the date using location. + final DateTime timeZoneDate = TZDateTime( + _timeZoneInfoToOlsonTimeZone(appTimeZoneId), + date.year, + date.month, + date.day, + date.hour, + date.minute, + date.second); + + final Duration offset = DateTime.now().timeZoneOffset; + + /// Convert the appointment time zone date to local date. + /// Eg., Nov 1 10AM(EST) UTC value as Nov 1 3(UTC) add local + /// time zone offset(IST +5.30) return Nov 1 8PM. + final DateTime localTimeZoneDate = + addDuration(timeZoneDate.toUtc(), offset); + + /// Resulted date as Nov 1 8PM but its time zone as EST so create the + /// local time date based on resulted date. + /// We does not use from method in TZDateTime because we does not + /// know the local time zone location. + convertedDate = DateTime( + localTimeZoneDate.year, + localTimeZoneDate.month, + localTimeZoneDate.day, + localTimeZoneDate.hour, + localTimeZoneDate.minute, + localTimeZoneDate.second); } - - //// Above mentioned converted date hold the date value which is equal to original date, but the time zone value changed. - //// E.g., Nov 3- 9.00 AM IST equal to Nov 2- 10.30 PM EST - //// So convert the Appointment time zone date to current time zone date. - convertedDate = DateTime( - date.year - (convertedDate.year - date.year), - date.month - (convertedDate.month - date.month), - date.day - (convertedDate.day - date.day), - date.hour - (convertedDate.hour - date.hour), - date.minute - (convertedDate.minute - date.minute), - date.second); } if (calendarTimeZoneId != null && calendarTimeZoneId != '') { @@ -1113,21 +1133,33 @@ DateTime _convertTimeToAppointmentTimeZone( if (calendarTimeZoneId == 'Dateline Standard Time') { actualConvertedDate = subtractDuration(convertedDate.toUtc(), const Duration(hours: 12)); + //// Above mentioned actual converted date hold the date value which is equal to converted date, but the time zone value changed. + //// So convert the schedule time zone date to current time zone date for rendering the appointment. + return DateTime( + convertedDate.year + (actualConvertedDate.year - convertedDate.year), + convertedDate.month + + (actualConvertedDate.month - convertedDate.month), + convertedDate.day + (actualConvertedDate.day - convertedDate.day), + convertedDate.hour + (actualConvertedDate.hour - convertedDate.hour), + convertedDate.minute + + (actualConvertedDate.minute - convertedDate.minute), + convertedDate.second); } else { - actualConvertedDate = TZDateTime.from( - convertedDate, _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); } - - //// 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); } return convertedDate; 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 9b141e033..2333fef39 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,11 +1,11 @@ part of calendar; _AppointmentView _getAppointmentView( - Appointment appointment, _AppointmentPainter appointmentPainter, + Appointment appointment, List<_AppointmentView> appointmentCollection, [int resourceIndex]) { _AppointmentView appointmentRenderer; - for (int i = 0; i < appointmentPainter._appointmentCollection.length; i++) { - final _AppointmentView view = appointmentPainter._appointmentCollection[i]; + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView view = appointmentCollection[i]; if (view.appointment == null) { appointmentRenderer = view; break; @@ -17,7 +17,7 @@ _AppointmentView _getAppointmentView( appointmentRenderer.appointment = appointment; appointmentRenderer.canReuse = false; appointmentRenderer.resourceIndex = resourceIndex; - appointmentPainter._appointmentCollection.add(appointmentRenderer); + appointmentCollection.add(appointmentRenderer); } appointmentRenderer.appointment = appointment; @@ -27,10 +27,13 @@ _AppointmentView _getAppointmentView( } void _createVisibleAppointments( - _AppointmentPainter appointmentPainter, int startIndex, int endIndex) { - for (int i = 0; i < appointmentPainter._appointmentCollection.length; i++) { - final _AppointmentView appointmentView = - appointmentPainter._appointmentCollection[i]; + List<_AppointmentView> appointmentCollection, + List visibleAppointments, + List visibleDates, + int startIndex, + int endIndex) { + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView appointmentView = appointmentCollection[i]; appointmentView.endIndex = -1; appointmentView.startIndex = -1; appointmentView.isSpanned = false; @@ -39,22 +42,22 @@ void _createVisibleAppointments( appointmentView.canReuse = true; } - if (appointmentPainter.visibleAppointments == null) { + if (visibleAppointments == null) { return; } - for (int i = 0; i < appointmentPainter.visibleAppointments.length; i++) { - final Appointment appointment = appointmentPainter.visibleAppointments[i]; + for (int i = 0; i < visibleAppointments.length; i++) { + final Appointment appointment = visibleAppointments[i]; if (!appointment._isSpanned && appointment._actualStartTime.day == appointment._actualEndTime.day && appointment._actualStartTime.month == appointment._actualEndTime.month) { final _AppointmentView appointmentView = - _createAppointmentView(appointmentPainter); + _createAppointmentView(appointmentCollection); appointmentView.appointment = appointment; appointmentView.canReuse = false; appointmentView.startIndex = - _getDateIndex(appointment._actualStartTime, appointmentPainter); + _getDateIndex(appointment._actualStartTime, visibleDates); /// Check the index value before the view start index then assign the /// start index as visible start index @@ -70,7 +73,7 @@ void _createVisibleAppointments( } appointmentView.endIndex = - _getDateIndex(appointment._actualEndTime, appointmentPainter); + _getDateIndex(appointment._actualEndTime, visibleDates); /// Check the index value after the view end index then assign the /// end index as visible end index @@ -85,17 +88,16 @@ void _createVisibleAppointments( appointmentView.endIndex = endIndex; } - if (!appointmentPainter._appointmentCollection - .contains(appointmentView)) { - appointmentPainter._appointmentCollection.add(appointmentView); + if (!appointmentCollection.contains(appointmentView)) { + appointmentCollection.add(appointmentView); } } else { final _AppointmentView appointmentView = - _createAppointmentView(appointmentPainter); + _createAppointmentView(appointmentCollection); appointmentView.appointment = appointment; appointmentView.canReuse = false; appointmentView.startIndex = - _getDateIndex(appointment._actualStartTime, appointmentPainter); + _getDateIndex(appointment._actualStartTime, visibleDates); /// Check the index value before the view start index then assign the /// start index as visible start index @@ -111,7 +113,7 @@ void _createVisibleAppointments( } appointmentView.endIndex = - _getDateIndex(appointment._actualEndTime, appointmentPainter); + _getDateIndex(appointment._actualEndTime, visibleDates); /// Check the index value after the view end index then assign the /// end index as visible end index @@ -127,13 +129,14 @@ void _createVisibleAppointments( } _createAppointmentInfoForSpannedAppointment( - appointmentView, appointmentPainter); + appointmentView, appointmentCollection); } } } void _createAppointmentInfoForSpannedAppointment( - _AppointmentView appointmentView, _AppointmentPainter appointmentPainter) { + _AppointmentView appointmentView, + List<_AppointmentView> appointmentCollection) { if (appointmentView.startIndex ~/ _kNumberOfDaysInWeek != appointmentView.endIndex ~/ _kNumberOfDaysInWeek) { final int endIndex = appointmentView.endIndex; @@ -143,23 +146,23 @@ void _createAppointmentInfoForSpannedAppointment( 1) .toInt(); appointmentView.isSpanned = true; - if (appointmentPainter._appointmentCollection != null && - !appointmentPainter._appointmentCollection.contains(appointmentView)) { - appointmentPainter._appointmentCollection.add(appointmentView); + if (appointmentCollection != null && + !appointmentCollection.contains(appointmentView)) { + appointmentCollection.add(appointmentView); } final _AppointmentView appointmentView1 = - _createAppointmentView(appointmentPainter); + _createAppointmentView(appointmentCollection); appointmentView1.appointment = appointmentView.appointment; appointmentView1.canReuse = false; appointmentView1.startIndex = appointmentView.endIndex + 1; appointmentView1.endIndex = endIndex; _createAppointmentInfoForSpannedAppointment( - appointmentView1, appointmentPainter); + appointmentView1, appointmentCollection); } else { appointmentView.isSpanned = true; - if (!appointmentPainter._appointmentCollection.contains(appointmentView)) { - appointmentPainter._appointmentCollection.add(appointmentView); + if (!appointmentCollection.contains(appointmentView)) { + appointmentCollection.add(appointmentView); } } } @@ -221,9 +224,9 @@ bool _isInterceptAppointments( return false; } -void _updateAppointmentPosition(_AppointmentPainter appointmentPainter) { - appointmentPainter._appointmentCollection - .sort((_AppointmentView app1, _AppointmentView app2) { +void _updateAppointmentPosition(List<_AppointmentView> appointmentCollection, + Map> indexAppointments) { + appointmentCollection.sort((_AppointmentView app1, _AppointmentView app2) { if (app1.appointment?._actualStartTime != null && app2.appointment?._actualStartTime != null) { return app1.appointment._actualStartTime @@ -232,17 +235,18 @@ void _updateAppointmentPosition(_AppointmentPainter appointmentPainter) { return 0; }); - appointmentPainter._appointmentCollection.sort( - (_AppointmentView app1, _AppointmentView app2) => - _orderAppointmentViewBySpanned(app1, app2)); + appointmentCollection.sort((_AppointmentView app1, _AppointmentView app2) => + _orderAppointmentViewBySpanned(app1, app2)); + + for (int j = 0; j < appointmentCollection.length; j++) { + final _AppointmentView appointmentView = appointmentCollection[j]; + if (appointmentView.canReuse || appointmentView.appointment == null) { + continue; + } - for (int j = 0; j < appointmentPainter._appointmentCollection.length; j++) { - final _AppointmentView appointmentView = - appointmentPainter._appointmentCollection[j]; appointmentView.position = 1; appointmentView.maxPositions = 1; - _setAppointmentPosition( - appointmentPainter._appointmentCollection, appointmentView, j); + _setAppointmentPosition(appointmentCollection, appointmentView, j); /// Add the appointment views to index appointment based on start and end /// index. It is used to get the visible index appointments. @@ -252,28 +256,26 @@ void _updateAppointmentPosition(_AppointmentPainter appointmentPainter) { /// Check the index already have appointments, if exists then add the /// current appointment to that collection, else create the index and /// create new collection with current appointment. - if (appointmentPainter._indexAppointments.containsKey(i)) { + if (indexAppointments.containsKey(i)) { final List<_AppointmentView> existingAppointments = - appointmentPainter._indexAppointments[i]; + indexAppointments[i]; existingAppointments.add(appointmentView); - appointmentPainter._indexAppointments[i] = existingAppointments; + indexAppointments[i] = existingAppointments; } else { - appointmentPainter._indexAppointments[i] = <_AppointmentView>[ - appointmentView - ]; + indexAppointments[i] = <_AppointmentView>[appointmentView]; } } } } -int _getDateIndex(DateTime date, _AppointmentPainter appointmentPainter) { - DateTime dateTime = appointmentPainter.visibleDates[ - appointmentPainter.visibleDates.length - _kNumberOfDaysInWeek]; +int _getDateIndex(DateTime date, List visibleDates) { + final int count = visibleDates.length; + DateTime dateTime = visibleDates[count - _kNumberOfDaysInWeek]; int row = 0; - for (int i = appointmentPainter.visibleDates.length - _kNumberOfDaysInWeek; + for (int i = count - _kNumberOfDaysInWeek; i >= 0; i -= _kNumberOfDaysInWeek) { - DateTime currentDate = appointmentPainter.visibleDates[i]; + DateTime currentDate = visibleDates[i]; currentDate = DateTime(currentDate.year, currentDate.month, currentDate.day, currentDate.hour, currentDate.minute, currentDate.second); if (currentDate.isBefore(date) || @@ -306,10 +308,10 @@ int _getDateIndex(DateTime date, _AppointmentPainter appointmentPainter) { } _AppointmentView _createAppointmentView( - _AppointmentPainter appointmentPainter) { + List<_AppointmentView> appointmentCollection) { _AppointmentView appointmentView; - for (int i = 0; i < appointmentPainter._appointmentCollection.length; i++) { - final _AppointmentView view = appointmentPainter._appointmentCollection[i]; + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView view = appointmentCollection[i]; if (view.canReuse) { appointmentView = view; break; @@ -329,10 +331,15 @@ _AppointmentView _createAppointmentView( } void _updateAppointment( - _AppointmentPainter appointmentPainter, int startIndex, int endIndex) { - _createVisibleAppointments(appointmentPainter, startIndex, endIndex); - if (appointmentPainter.visibleAppointments != null && - appointmentPainter.visibleAppointments.isNotEmpty) { - _updateAppointmentPosition(appointmentPainter); + List visibleAppointments, + List<_AppointmentView> appointmentCollection, + List visibleDates, + Map> indexAppointments, + int startIndex, + int endIndex) { + _createVisibleAppointments(appointmentCollection, visibleAppointments, + visibleDates, startIndex, endIndex); + if (visibleAppointments != null && visibleAppointments.isNotEmpty) { + _updateAppointmentPosition(appointmentCollection, indexAppointments); } } 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 86626a800..9ddcd02f9 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/agenda_view_layout.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/agenda_view_layout.dart @@ -1,7 +1,7 @@ part of calendar; -class _AgendaViewPainter extends CustomPainter { - _AgendaViewPainter( +class _AgendaViewLayout extends StatefulWidget { + _AgendaViewLayout( this.monthViewSettings, this.scheduleViewSettings, this.selectedDate, @@ -12,9 +12,12 @@ class _AgendaViewPainter extends CustomPainter { this.calendarTheme, this.agendaViewNotifier, this.appointmentTimeTextFormat, - this._timeLabelWidth, - this.textScaleFactor) - : super(repaint: agendaViewNotifier); + this.timeLabelWidth, + this.textScaleFactor, + this.isMobilePlatform, + this.appointmentBuilder, + this.width, + this.height); final MonthViewSettings monthViewSettings; final ScheduleViewSettings scheduleViewSettings; @@ -25,19 +28,694 @@ class _AgendaViewPainter extends CustomPainter { final SfCalendarThemeData calendarTheme; final ValueNotifier<_ScheduleViewHoveringDetails> agendaViewNotifier; final SfLocalizations localizations; - final double _timeLabelWidth; + final double timeLabelWidth; final String appointmentTimeTextFormat; + final CalendarAppointmentBuilder appointmentBuilder; final double textScaleFactor; + final bool isMobilePlatform; + final double width; + final double height; + + @override + _AgendaViewLayoutState createState() => _AgendaViewLayoutState(); +} + +class _AgendaViewLayoutState extends State<_AgendaViewLayout> { + /// It holds the appointment views for the visible appointments. + List<_AppointmentView> _appointmentCollection; + + /// It holds the children of the widget, it holds null or empty when + /// appointment builder is null. + List _children; + + @override + void initState() { + _appointmentCollection = <_AppointmentView>[]; + _children = []; + _updateAppointmentDetails(); + super.initState(); + } + + @override + void didUpdateWidget(_AgendaViewLayout oldWidget) { + if (widget.appointments != oldWidget.appointments || + widget.selectedDate != oldWidget.selectedDate || + widget.timeLabelWidth != oldWidget.timeLabelWidth || + widget.width != oldWidget.width || + widget.height != oldWidget.height || + widget.appointmentBuilder != oldWidget.appointmentBuilder) { + _updateAppointmentDetails(); + } + + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + _children ??= []; + + /// Create the widgets when appointment builder is not null. + if (_children.isEmpty && + widget.appointmentBuilder != null && + _appointmentCollection != null) { + final int appointmentCount = _appointmentCollection.length; + for (int i = 0; i < appointmentCount; i++) { + final _AppointmentView view = _appointmentCollection[i]; + + /// Check the appointment view have appointment, if not then the + /// appointment view is not valid or it will be used for reusing view. + if (view.appointment == null || view.appointmentRect == null) { + continue; + } + final CalendarAppointmentDetails details = CalendarAppointmentDetails( + appointments: + List.unmodifiable([view.appointment._data ?? view.appointment]), + date: widget.selectedDate, + bounds: view.appointmentRect.outerRect); + final Widget child = widget.appointmentBuilder(context, details); + + /// Throw exception when builder return widget is null. + assert(child != null, 'Widget must not be null'); + _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); + } + + void _updateAppointmentDetails() { + double yPosition = 5; + const double padding = 5; + + final double totalAgendaViewWidth = widget.width + widget.timeLabelWidth; + final bool useMobilePlatformUI = + _isMobileLayoutUI(totalAgendaViewWidth, widget.isMobilePlatform); + _resetAppointmentView(_appointmentCollection); + _children.clear(); + if (widget.selectedDate == null || + widget.appointments == null || + widget.appointments.isEmpty) { + return; + } + + final bool isLargerScheduleUI = + widget.scheduleViewSettings == null || useMobilePlatformUI + ? false + : true; + + widget.appointments.sort((Appointment app1, Appointment app2) => + app1._actualStartTime.compareTo(app2._actualStartTime)); + widget.appointments.sort((Appointment app1, Appointment app2) => + _orderAppointmentsAscending(app1.isAllDay, app2.isAllDay)); + widget.appointments.sort((Appointment app1, Appointment app2) => + _orderAppointmentsAscending(app1._isSpanned, app2._isSpanned)); + final double agendaItemHeight = _getScheduleAppointmentHeight( + widget.monthViewSettings, widget.scheduleViewSettings); + final double agendaAllDayItemHeight = _getScheduleAllDayAppointmentHeight( + widget.monthViewSettings, widget.scheduleViewSettings); + + for (int i = 0; i < widget.appointments.length; i++) { + final Appointment appointment = widget.appointments[i]; + final bool isSpanned = + appointment._actualEndTime.day != appointment._actualStartTime.day || + appointment._isSpanned; + final double appointmentHeight = + (appointment.isAllDay || isSpanned) && !isLargerScheduleUI + ? agendaAllDayItemHeight + : agendaItemHeight; + final Rect rect = Rect.fromLTWH( + padding, yPosition, widget.width - (2 * padding), appointmentHeight); + final Radius cornerRadius = Radius.circular( + (appointmentHeight * 0.1) > 5 ? 5 : (appointmentHeight * 0.1)); + yPosition += appointmentHeight + padding; + _AppointmentView appointmentRenderer; + for (int i = 0; i < _appointmentCollection.length; i++) { + final _AppointmentView view = _appointmentCollection[i]; + if (view.appointment == null) { + appointmentRenderer = view; + break; + } + } + + if (appointmentRenderer == null) { + appointmentRenderer = _AppointmentView(); + appointmentRenderer.appointment = appointment; + appointmentRenderer.canReuse = false; + _appointmentCollection.add(appointmentRenderer); + } + + appointmentRenderer.canReuse = false; + appointmentRenderer.appointment = appointment; + 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}) + : super(children: widgets); + + final MonthViewSettings monthViewSettings; + final ScheduleViewSettings scheduleViewSettings; + final DateTime selectedDate; + final List appointments; + final bool isRTL; + final String locale; + final SfCalendarThemeData calendarTheme; + final ValueNotifier<_ScheduleViewHoveringDetails> agendaViewNotifier; + final SfLocalizations localizations; + final double timeLabelWidth; + final String appointmentTimeTextFormat; + final double textScaleFactor; + final bool isMobilePlatform; + final List<_AppointmentView> appointmentCollection; + final double width; + final double height; + + @override + _AgendaViewRenderObject createRenderObject(BuildContext context) { + return _AgendaViewRenderObject( + monthViewSettings, + scheduleViewSettings, + selectedDate, + appointments, + isRTL, + locale, + localizations, + calendarTheme, + agendaViewNotifier, + appointmentTimeTextFormat, + timeLabelWidth, + textScaleFactor, + isMobilePlatform, + appointmentCollection, + width, + height); + } + + @override + void updateRenderObject( + BuildContext context, _AgendaViewRenderObject renderObject) { + renderObject + ..monthViewSettings = monthViewSettings + ..scheduleViewSettings = scheduleViewSettings + ..selectedDate = selectedDate + ..appointments = appointments + ..isRTL = isRTL + ..locale = locale + ..localizations = localizations + ..calendarTheme = calendarTheme + ..agendaViewNotifier = agendaViewNotifier + ..appointmentTimeTextFormat = appointmentTimeTextFormat + ..timeLabelWidth = timeLabelWidth + ..textScaleFactor = textScaleFactor + ..appointmentCollection = appointmentCollection + ..width = width + ..height = height; + } +} + +class _AgendaViewRenderObject extends RenderBox + with ContainerRenderObjectMixin { + _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); + + final bool isMobilePlatform; + + double _height; + + double get height => _height; + + set height(double value) { + if (_height == value) { + return; + } + + _height = value; + markNeedsLayout(); + } + + double _width; + + double get width => _width; + + set width(double value) { + if (_width == value) { + return; + } + + _width = value; + markNeedsLayout(); + } + + double _textScaleFactor; + + double get textScaleFactor => _textScaleFactor; + + set textScaleFactor(double value) { + if (_textScaleFactor == value) { + return; + } + + _textScaleFactor = value; + markNeedsPaint(); + } + + MonthViewSettings _monthViewSettings; + + MonthViewSettings get monthViewSettings => _monthViewSettings; + + set monthViewSettings(MonthViewSettings value) { + if (_monthViewSettings == value) { + return; + } + + _monthViewSettings = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + ScheduleViewSettings _scheduleViewSettings; + + ScheduleViewSettings get scheduleViewSettings => _scheduleViewSettings; + + set scheduleViewSettings(ScheduleViewSettings value) { + if (_scheduleViewSettings == value) { + return; + } + + _scheduleViewSettings = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + String _locale; + + String get locale => _locale; + + set locale(String value) { + if (_locale == value) { + return; + } + + _locale = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + SfLocalizations _localizations; + + SfLocalizations get localizations => _localizations; + + set localizations(SfLocalizations value) { + if (_localizations == value) { + return; + } + + _localizations = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + String _appointmentTimeTextFormat; + + String get appointmentTimeTextFormat => _appointmentTimeTextFormat; + + set appointmentTimeTextFormat(String value) { + if (_appointmentTimeTextFormat == value) { + return; + } + + _appointmentTimeTextFormat = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + double _timeLabelWidth; + + double get timeLabelWidth => _timeLabelWidth; + + set timeLabelWidth(double value) { + if (_timeLabelWidth == value) { + return; + } + + _timeLabelWidth = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + DateTime _selectedDate; + + DateTime get selectedDate => _selectedDate; + + set selectedDate(DateTime value) { + if (_selectedDate == value) { + return; + } + + _selectedDate = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + List _appointments; + + List get appointments => _appointments; + + set appointments(List value) { + if (_appointments == value) { + return; + } + + _appointments = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + List<_AppointmentView> _appointmentCollection; + + List<_AppointmentView> get appointmentCollection => _appointmentCollection; + + set appointmentCollection(List<_AppointmentView> value) { + if (_appointmentCollection == value) { + return; + } + + _appointmentCollection = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + bool _isRTL; + + bool get isRTL => _isRTL; + + set isRTL(bool value) { + if (_isRTL == value) { + return; + } + + _isRTL = value; + markNeedsPaint(); + } + + SfCalendarThemeData _calendarTheme; + + SfCalendarThemeData get calendarTheme => _calendarTheme; + + set calendarTheme(SfCalendarThemeData value) { + if (_calendarTheme == value) { + return; + } + + _calendarTheme = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + ValueNotifier<_ScheduleViewHoveringDetails> _agendaViewNotifier; + + ValueNotifier<_ScheduleViewHoveringDetails> get agendaViewNotifier => + _agendaViewNotifier; + + set agendaViewNotifier(ValueNotifier<_ScheduleViewHoveringDetails> value) { + if (_agendaViewNotifier == value) { + return; + } + + _agendaViewNotifier?.removeListener(markNeedsPaint); + _agendaViewNotifier = value; + _agendaViewNotifier?.addListener(markNeedsPaint); + } + Paint _rectPainter; TextPainter _textPainter; + /// attach will called when the render object rendered in view. @override - void paint(Canvas canvas, Size size) { - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + void attach(PipelineOwner owner) { + super.attach(owner); + _agendaViewNotifier?.addListener(markNeedsPaint); + } + + /// detach will called when the render object removed from view. + @override + void detach() { + _agendaViewNotifier?.removeListener(markNeedsPaint); + super.detach(); + } + + @override + void setupParentData(RenderObject child) { + if (child.parentData is! _CalendarParentData) { + child.parentData = _CalendarParentData(); + } + } + + @override + void performLayout() { + final Size widgetSize = constraints.biggest; + size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height); + RenderBox child = firstChild; + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView appointmentView = appointmentCollection[i]; + if (appointmentView.appointment == null || + child == null || + appointmentView.appointmentRect == null) { + continue; + } + + child.layout(constraints.copyWith( + minHeight: appointmentView.appointmentRect.height, + maxHeight: appointmentView.appointmentRect.height, + minWidth: appointmentView.appointmentRect.width, + maxWidth: appointmentView.appointmentRect.width)); + child = childAfter(child); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + RenderBox child = firstChild; + final bool _isNeedDefaultPaint = childCount == 0; + _rectPainter = _rectPainter ?? Paint(); + final double totalAgendaViewWidth = size.width + _timeLabelWidth; + final bool useMobilePlatformUI = + _isMobileLayoutUI(totalAgendaViewWidth, isMobilePlatform); + final bool isLargerScheduleUI = + scheduleViewSettings == null || useMobilePlatformUI ? false : true; + if (_isNeedDefaultPaint) { + _textPainter = _textPainter ?? TextPainter(); + _drawDefaultUI(context.canvas, isLargerScheduleUI, offset); + } else { + const double padding = 5.0; + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView appointmentView = appointmentCollection[i]; + if (appointmentView.appointment == null || + child == null || + appointmentView.appointmentRect == null) { + continue; + } + + final RRect rect = appointmentView.appointmentRect.shift(offset); + child.paint(context, Offset(rect.left, rect.top)); + if (agendaViewNotifier.value != null && + isSameDate(agendaViewNotifier.value.hoveringDate, selectedDate)) { + _addMouseHovering( + context.canvas, size, rect, isLargerScheduleUI, padding); + } + + child = childAfter(child); + } + } + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config.isSemanticBoundary = true; + } + + @override + void assembleSemanticsNode( + SemanticsNode node, + SemanticsConfiguration config, + Iterable children, + ) { + final List semantics = _getSemanticsBuilder(size); + final List semanticsNodes = []; + for (int i = 0; i < semantics.length; i++) { + final CustomPainterSemantics currentSemantics = semantics[i]; + final SemanticsNode newChild = SemanticsNode( + key: currentSemantics.key, + ); + + final SemanticsProperties properties = currentSemantics.properties; + final SemanticsConfiguration config = SemanticsConfiguration(); + if (properties.label != null) { + config.label = properties.label; + } + if (properties.textDirection != null) { + config.textDirection = properties.textDirection; + } + + newChild.updateWith( + config: config, + // As of now CustomPainter does not support multiple tree levels. + childrenInInversePaintOrder: const [], + ); + + newChild + ..rect = currentSemantics.rect + ..transform = currentSemantics.transform + ..tags = currentSemantics.tags; + + semanticsNodes.add(newChild); + } + + final List finalChildren = []; + finalChildren.addAll(semanticsNodes); + finalChildren.addAll(children); + + super.assembleSemanticsNode(node, config, finalChildren); + } + + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + return; + } + + List _getSemanticsBuilder(Size size) { + final List semanticsBuilder = + []; + if (selectedDate == null) { + 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: DateFormat('EEEEE').format(selectedDate).toString() + + DateFormat('dd/MMMM/yyyy').format(selectedDate).toString() + + ', ' + 'No events', + textDirection: TextDirection.ltr, + ), + )); + } else if (selectedDate != null && + appointmentCollection != null && + appointmentCollection.isNotEmpty) { + 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: _getAppointmentText(appointmentView.appointment), + textDirection: TextDirection.ltr, + ), + )); + } + } + + return semanticsBuilder; + } + + void _drawDefaultUI(Canvas canvas, bool isLargerScheduleUI, Offset offset) { _rectPainter = _rectPainter ?? Paint(); _rectPainter.isAntiAlias = true; - double yPosition = 5; - double xPosition = 5; + double yPosition = offset.dy + 5; + double xPosition = offset.dx + 5; const double padding = 5; if (selectedDate == null || appointments == null || appointments.isEmpty) { @@ -45,76 +723,60 @@ class _AgendaViewPainter extends CustomPainter { return; } - final bool isScheduleWebUI = scheduleViewSettings != null && - (kIsWeb && (size.width + _timeLabelWidth) > _kMobileViewWidth) - ? true - : false; - - appointments.sort((Appointment app1, Appointment app2) => - app1._actualStartTime.compareTo(app2._actualStartTime)); - appointments.sort((Appointment app1, Appointment app2) => - _orderAppointmentsAscending(app1.isAllDay, app2.isAllDay)); - appointments.sort((Appointment app1, Appointment app2) => - _orderAppointmentsAscending(app1._isSpanned, app2._isSpanned)); final TextStyle appointmentTextStyle = monthViewSettings != null ? monthViewSettings.agendaStyle.appointmentTextStyle ?? TextStyle(color: Colors.white, fontSize: 13, fontFamily: 'Roboto') : scheduleViewSettings.appointmentTextStyle ?? TextStyle( - color: isScheduleWebUI && + color: isLargerScheduleUI && calendarTheme.brightness == Brightness.light ? Colors.black87 : Colors.white, fontSize: 13, fontFamily: 'Roboto'); - final double agendaItemHeight = - _getScheduleAppointmentHeight(monthViewSettings, scheduleViewSettings); - final double agendaAllDayItemHeight = _getScheduleAllDayAppointmentHeight( - monthViewSettings, scheduleViewSettings); //// Draw Appointments - for (int i = 0; i < appointments.length; i++) { - final Appointment appointment = appointments[i]; - xPosition = 5; + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView appointmentView = appointmentCollection[i]; + if (appointmentView.appointment == null) { + continue; + } + + final Appointment appointment = appointmentView.appointment; _rectPainter.color = appointment.color; final bool isSpanned = appointment._actualEndTime.day != appointment._actualStartTime.day || appointment._isSpanned; - final double appointmentHeight = - (appointment.isAllDay || isSpanned) && !isScheduleWebUI - ? agendaAllDayItemHeight - : agendaItemHeight; - final Rect rect = Rect.fromLTWH(xPosition, yPosition, - size.width - xPosition - padding, appointmentHeight); - final Radius cornerRadius = Radius.circular( - (appointmentHeight * 0.1) > 5 ? 5 : (appointmentHeight * 0.1)); + final double appointmentHeight = appointmentView.appointmentRect.height; + final RRect rect = appointmentView.appointmentRect.shift(offset); + xPosition = rect.left; + yPosition = rect.top; /// Web view does not highlighted by background - if (!isScheduleWebUI) { - canvas.drawRRect( - RRect.fromRectAndRadius(rect, cornerRadius), _rectPainter); + if (!isLargerScheduleUI) { + canvas.drawRRect(rect, _rectPainter); } final TextSpan span = TextSpan(text: appointment.subject, style: appointmentTextStyle); _updateTextPainterProperties(span); double timeWidth = - isScheduleWebUI ? (size.width - (2 * padding)) * 0.3 : 0; + isLargerScheduleUI ? (size.width - (2 * padding)) * 0.3 : 0; timeWidth = timeWidth > 200 ? 200 : timeWidth; xPosition += timeWidth; final bool isRecurrenceAppointment = appointment.recurrenceRule != null && appointment.recurrenceRule.isNotEmpty; - final double textSize = _getTextSize(rect, appointmentTextStyle); + final double textSize = + _getTextSize(rect, appointmentTextStyle, isMobilePlatform); double topPadding = 0; /// Draw web schedule view. - if (isScheduleWebUI) { + if (isLargerScheduleUI) { topPadding = _addScheduleViewForWeb( canvas, size, - agendaItemHeight, padding, xPosition, yPosition, @@ -123,12 +785,15 @@ class _AgendaViewPainter extends CustomPainter { isSpanned || isRecurrenceAppointment, textSize, appointment, - appointmentTextStyle); + appointmentTextStyle, + offset); if (isSpanned) { - final TextSpan icon = _getSpanIcon(appointmentTextStyle.color, - kIsWeb ? textSize / 1.5 : textSize, isRTL ? false : true); - _drawIcon(canvas, size, textSize, rect, padding, isScheduleWebUI, - cornerRadius, icon, appointmentHeight, topPadding, true, false); + final TextSpan icon = _getSpanIcon( + appointmentTextStyle.color, + isMobilePlatform ? textSize : textSize / 1.5, + isRTL ? false : true); + _drawIcon(canvas, size, textSize, rect, padding, isLargerScheduleUI, + rect.tlRadius, icon, appointmentHeight, topPadding, true, false); } } else { /// Draws spanning appointment UI for schedule view. @@ -143,8 +808,9 @@ class _AgendaViewPainter extends CustomPainter { appointmentTextStyle, appointmentHeight, rect, - isScheduleWebUI, - cornerRadius); + isMobilePlatform, + isLargerScheduleUI, + rect.tlRadius); } //// Draw Appointments except All day appointment else if (!appointment.isAllDay) { @@ -186,8 +852,8 @@ class _AgendaViewPainter extends CustomPainter { textSize, rect, padding, - isScheduleWebUI, - cornerRadius, + isLargerScheduleUI, + rect.tlRadius, icon, appointmentHeight, topPadding, @@ -197,23 +863,22 @@ class _AgendaViewPainter extends CustomPainter { if (agendaViewNotifier.value != null && isSameDate(agendaViewNotifier.value.hoveringDate, selectedDate)) { - _addMouseHovering(canvas, size, rect, isScheduleWebUI, padding); + _addMouseHovering(canvas, size, rect, isLargerScheduleUI, padding); } - - yPosition += appointmentHeight + padding; } } - double _getTextSize(Rect rect, TextStyle appointmentTextStyle) { + double _getTextSize( + 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 = kIsWeb - ? appointmentTextStyle.fontSize != null + final double textSize = isMobilePlatform + ? appointmentTextStyle.fontSize ?? defaultFontSize + : appointmentTextStyle.fontSize != null ? appointmentTextStyle.fontSize * 1.5 - : defaultFontSize * 1.5 - : appointmentTextStyle.fontSize ?? defaultFontSize; + : defaultFontSize * 1.5; if (rect.width < textSize || rect.height < textSize) { return rect.width > rect.height ? rect.height : rect.width; } @@ -225,9 +890,9 @@ class _AgendaViewPainter extends CustomPainter { Canvas canvas, Size size, double textSize, - Rect rect, + RRect rect, double padding, - bool isScheduleWebUI, + bool isLargerScheduleUI, Radius cornerRadius, TextSpan icon, double appointmentHeight, @@ -239,7 +904,7 @@ class _AgendaViewPainter extends CustomPainter { _textPainter.layout( minWidth: 0, maxWidth: size.width - (2 * padding) - padding); final double iconSize = textSize + 8; - if (!isScheduleWebUI) { + if (!isLargerScheduleUI) { if (isRTL) { canvas.drawRRect( RRect.fromRectAndRadius( @@ -359,8 +1024,9 @@ class _AgendaViewPainter extends CustomPainter { Appointment appointment, TextStyle appointmentTextStyle, double appointmentHeight, - Rect rect, - bool isScheduleWebUI, + RRect rect, + bool isMobilePlatform, + bool isLargerScheduleUI, Radius cornerRadius) { final TextSpan span = TextSpan( text: _getSpanAppointmentText(appointment, selectedDate), @@ -371,7 +1037,8 @@ class _AgendaViewPainter extends CustomPainter { isAllDay: false, isSpanned: true); final bool isNeedSpanIcon = !isSameDate(appointment._exactEndTime, selectedDate); - final double textSize = _getTextSize(rect, appointmentTextStyle); + final double textSize = + _getTextSize(rect, appointmentTextStyle, isMobilePlatform); /// Icon padding 8 and 2 additional padding final double iconSize = isNeedSpanIcon ? textSize + 10 : 0; @@ -391,8 +1058,8 @@ class _AgendaViewPainter extends CustomPainter { } final TextSpan icon = _getSpanIcon(appointmentTextStyle.color, - kIsWeb ? textSize / 1.5 : textSize, isRTL ? false : true); - _drawIcon(canvas, size, textSize, rect, padding, isScheduleWebUI, + isMobilePlatform ? textSize : textSize / 1.5, isRTL ? false : true); + _drawIcon(canvas, size, textSize, rect, padding, isLargerScheduleUI, cornerRadius, icon, appointmentHeight, topPadding, true, false); return topPadding; } @@ -428,7 +1095,6 @@ class _AgendaViewPainter extends CustomPainter { double _addScheduleViewForWeb( Canvas canvas, Size size, - double agendaItemHeight, double padding, double xPosition, double yPosition, @@ -437,12 +1103,13 @@ class _AgendaViewPainter extends CustomPainter { bool isNeedIcon, double textSize, Appointment appointment, - TextStyle appointmentTextStyle) { + TextStyle appointmentTextStyle, + Offset offset) { _textPainter.textScaleFactor = textScaleFactor; - final double centerYPosition = agendaItemHeight / 2; + final double centerYPosition = appointmentHeight / 2; final double circleRadius = centerYPosition > padding ? padding : centerYPosition - 2; - final double circleStartPosition = 3 * circleRadius; + final double circleStartPosition = offset.dx + (3 * circleRadius); canvas.drawCircle( Offset(isRTL ? size.width - circleStartPosition : circleStartPosition, yPosition + centerYPosition), @@ -484,7 +1151,7 @@ class _AgendaViewPainter extends CustomPainter { _textPainter.text = span; _textPainter.layout(minWidth: 0, maxWidth: timeWidth - padding); - xPosition = padding + circleWidth; + xPosition = offset.dx + padding + circleWidth; if (isRTL) { xPosition = size.width - _textPainter.width - (3 * padding) - circleWidth; } @@ -496,20 +1163,20 @@ class _AgendaViewPainter extends CustomPainter { return topPadding; } - void _addMouseHovering(Canvas canvas, Size size, Rect rect, - bool isScheduleWebUI, double padding) { + void _addMouseHovering(Canvas canvas, Size size, RRect rect, + bool isLargerScheduleUI, double padding) { _rectPainter ??= Paint(); if (rect.left < agendaViewNotifier.value.hoveringOffset.dx && rect.right > agendaViewNotifier.value.hoveringOffset.dx && rect.top < agendaViewNotifier.value.hoveringOffset.dy && rect.bottom > agendaViewNotifier.value.hoveringOffset.dy) { - if (isScheduleWebUI) { + if (isLargerScheduleUI) { _rectPainter.color = Colors.grey.withOpacity(0.1); const double viewPadding = 2; canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromLTWH( - 0, + rect.left - padding, rect.top + viewPadding, size.width - (isRTL ? viewPadding : padding), rect.height - (2 * viewPadding)), @@ -520,90 +1187,11 @@ class _AgendaViewPainter extends CustomPainter { calendarTheme.selectionBorderColor.withOpacity(0.4); _rectPainter.style = PaintingStyle.stroke; _rectPainter.strokeWidth = 2; - canvas.drawRect(rect, _rectPainter); + canvas.drawRect(rect.outerRect, _rectPainter); _rectPainter.style = PaintingStyle.fill; } } } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - return true; - } - - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the list - /// of custom painter semantics which contains the rect area and the semantics - /// properties for accessibility - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilder(size); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - return true; - } - - List _getSemanticsBuilder(Size size) { - final List semanticsBuilder = - []; - const double left = 5.0; - double top = 5.0; - const double padding = 5.0; - final double agendaItemHeight = - _getScheduleAppointmentHeight(monthViewSettings, scheduleViewSettings); - final double agendaAllDayItemHeight = _getScheduleAllDayAppointmentHeight( - monthViewSettings, scheduleViewSettings); - if (selectedDate == null) { - 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: DateFormat('EEEEE').format(selectedDate).toString() + - DateFormat('dd/MMMM/yyyy').format(selectedDate).toString() + - ', ' - 'No events', - textDirection: TextDirection.ltr, - ), - )); - } else if (selectedDate != null && - appointments != null && - appointments.isNotEmpty) { - final bool isScheduleWebUI = - scheduleViewSettings != null && kIsWeb ? true : false; - for (int i = 0; i < appointments.length; i++) { - final Appointment currentAppointment = appointments[i]; - final double height = (currentAppointment.isAllDay || - currentAppointment._actualEndTime.day != - currentAppointment._actualStartTime.day || - currentAppointment._isSpanned) && - !isScheduleWebUI - ? agendaAllDayItemHeight - : agendaItemHeight; - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(left, top, size.width - left - padding, height), - properties: SemanticsProperties( - label: _getAppointmentText(currentAppointment), - textDirection: TextDirection.ltr, - ), - )); - top += height + padding; - } - } - - return semanticsBuilder; - } } class _AgendaDateTimePainter extends CustomPainter { @@ -618,7 +1206,8 @@ class _AgendaDateTimePainter extends CustomPainter { this.agendaDateNotifier, this.viewWidth, this.isRTL, - this.textScaleFactor) + this.textScaleFactor, + this.isMobilePlatform) : super(repaint: agendaDateNotifier); final DateTime selectedDate; @@ -632,6 +1221,7 @@ class _AgendaDateTimePainter extends CustomPainter { final double viewWidth; final bool isRTL; final double textScaleFactor; + final bool isMobilePlatform; Paint _linePainter; TextPainter _textPainter; @@ -645,6 +1235,8 @@ class _AgendaDateTimePainter extends CustomPainter { return; } + final bool useMobilePlatformUI = + _isMobileLayoutUI(viewWidth, isMobilePlatform); final bool isToday = isSameDate(selectedDate, DateTime.now()); TextStyle dateTextStyle, dayTextStyle; if (monthViewSettings != null) { @@ -654,21 +1246,21 @@ class _AgendaDateTimePainter extends CustomPainter { calendarTheme.agendaDateTextStyle; } else { dayTextStyle = scheduleViewSettings.dayHeaderSettings.dayTextStyle ?? - ((kIsWeb && viewWidth > _kMobileViewWidth) - ? TextStyle( + (useMobilePlatformUI + ? calendarTheme.agendaDayTextStyle + : TextStyle( color: calendarTheme.agendaDayTextStyle.color, fontSize: 9, fontFamily: 'Roboto', - fontWeight: FontWeight.w500) - : calendarTheme.agendaDayTextStyle); + fontWeight: FontWeight.w500)); dateTextStyle = scheduleViewSettings.dayHeaderSettings.dateTextStyle ?? - ((kIsWeb && viewWidth > _kMobileViewWidth) - ? TextStyle( + (useMobilePlatformUI + ? calendarTheme.agendaDateTextStyle + : TextStyle( color: calendarTheme.agendaDateTextStyle.color, fontSize: 18, fontFamily: 'Roboto', - fontWeight: FontWeight.normal) - : calendarTheme.agendaDateTextStyle); + fontWeight: FontWeight.normal)); } final Color selectedDayTextColor = isToday @@ -695,10 +1287,9 @@ class _AgendaDateTimePainter extends CustomPainter { } /// Draw day label other than web schedule view. - if (scheduleViewSettings == null || - (!kIsWeb || (kIsWeb && viewWidth < _kMobileViewWidth))) { - _addDayLabelForMobile( - canvas, size, padding, dayTextStyle, dateTextStyle, isToday); + if (scheduleViewSettings == null || useMobilePlatformUI) { + _addDayLabelForMobile(canvas, size, padding, dayTextStyle, dateTextStyle, + isToday, isMobilePlatform); } else { _addDayLabelForWeb( canvas, size, padding, dayTextStyle, dateTextStyle, isToday); @@ -715,8 +1306,14 @@ class _AgendaDateTimePainter extends CustomPainter { _textPainter.textScaleFactor = textScaleFactor; } - void _addDayLabelForMobile(Canvas canvas, Size size, double padding, - TextStyle dayTextStyle, TextStyle dateTextStyle, bool isToday) { + void _addDayLabelForMobile( + Canvas canvas, + Size size, + double padding, + TextStyle dayTextStyle, + TextStyle dateTextStyle, + bool isToday, + bool isMobile) { //// Draw Weekday final String dayTextFormat = scheduleViewSettings != null ? scheduleViewSettings.dayHeaderSettings.dayFormat @@ -756,7 +1353,7 @@ class _AgendaDateTimePainter extends CustomPainter { /// padding added between date and day labels in web, to avoid the /// hovering effect overlapping issue. - if (kIsWeb && !isToday) { + if (!isMobile && !isToday) { yPosition = weekDayHeight + padding + inBetweenPadding; } if (agendaDateNotifier.value != null && 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 bfaa69f21..0478513ac 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 @@ -11,12 +11,12 @@ class _SelectionDetails { final DateTime selectedDate; } -class _AllDayAppointmentPainter extends CustomPainter { - _AllDayAppointmentPainter( +class _AllDayAppointmentLayout extends StatefulWidget { + _AllDayAppointmentLayout( this.calendar, this.view, this.visibleDates, - this.visibleAppointment, + this.visibleAppointments, this.timeLabelWidth, this.allDayPainterHeight, this.isExpandable, @@ -26,21 +26,26 @@ class _AllDayAppointmentPainter extends CustomPainter { this.repaintNotifier, this.allDayHoverPosition, this.textScaleFactor, - {this.updateCalendarState}) - : super(repaint: repaintNotifier); + this.isMobilePlatform, + this.width, + this.height, + {this.updateCalendarState}); final SfCalendar calendar; final CalendarView view; final List visibleDates; - final List visibleAppointment; + final List visibleAppointments; final ValueNotifier<_SelectionDetails> repaintNotifier; final _UpdateCalendarState updateCalendarState; final double timeLabelWidth; final double allDayPainterHeight; final bool isRTL; final SfCalendarThemeData calendarTheme; - final Offset allDayHoverPosition; + final ValueNotifier allDayHoverPosition; final double textScaleFactor; + final bool isMobilePlatform; + final double height; + final double width; //// is expandable variable used to indicate whether the all day layout expandable or not. final bool isExpandable; @@ -48,28 +53,748 @@ class _AllDayAppointmentPainter extends CustomPainter { //// is expanding variable used to identify the animation currently running or not. //// It is used to restrict the expander icon show on initial animation. final bool isExpanding; - double _cellWidth; + + @override + _AllDayAppointmentLayoutState createState() => + _AllDayAppointmentLayoutState(); +} + +class _AllDayAppointmentLayoutState extends State<_AllDayAppointmentLayout> { + final _UpdateCalendarStateDetails _updateCalendarStateDetails = + _UpdateCalendarStateDetails(); + + /// It holds the appointment list based on its visible index value. + Map> _indexAppointments; + + /// It holds the more appointment index appointment counts based on its index. + Map _moreAppointmentIndex; + + /// It holds the appointment views for the visible appointments. + List<_AppointmentView> _appointmentCollection; + + /// It holds the children of the widget, it holds null or empty when + /// appointment builder is null. + List _children; + + @override + void initState() { + _children = []; + widget.updateCalendarState(_updateCalendarStateDetails); + _updateAppointmentDetails(); + super.initState(); + } + + @override + void didUpdateWidget(_AllDayAppointmentLayout oldWidget) { + widget.updateCalendarState(_updateCalendarStateDetails); + if (widget.visibleDates != oldWidget.visibleDates || + widget.width != oldWidget.width || + widget.height != oldWidget.height || + widget.allDayPainterHeight != oldWidget.allDayPainterHeight || + widget.calendar != oldWidget.calendar || + widget.view != oldWidget.view || + widget.isRTL != oldWidget.isRTL || + widget.isExpandable != oldWidget.isExpandable || + widget.visibleAppointments != oldWidget.visibleAppointments) { + _updateAppointmentDetails(); + _children.clear(); + } + + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + _children ??= []; + + /// Create the widgets when appointment builder is not null. + if (_children.isEmpty && + _appointmentCollection != null && + widget.calendar.appointmentBuilder != null) { + for (int i = 0; i < _appointmentCollection.length; i++) { + final _AppointmentView appointmentView = _appointmentCollection[i]; + + /// Check the appointment view have appointment, if not then the + /// appointment view is not valid or it will be placed on in expandable + /// region. + if (appointmentView.appointment == null || + appointmentView.appointmentRect == null) { + continue; + } + + final DateTime date = DateTime( + appointmentView.appointment._actualStartTime.year, + appointmentView.appointment._actualStartTime.month, + appointmentView.appointment._actualStartTime.day); + final Widget child = widget.calendar.appointmentBuilder( + context, + CalendarAppointmentDetails( + date: date, + bounds: Rect.fromLTWH( + appointmentView.appointmentRect.left, + appointmentView.appointmentRect.top, + appointmentView.appointmentRect.width, + appointmentView.appointmentRect.right), + appointments: List.unmodifiable([ + appointmentView.appointment._data ?? + appointmentView.appointment + ]), + isMoreAppointmentRegion: false)); + + /// Throw exception when builder return widget is null. + assert(child != null, 'Widget must not be null'); + _children.add(RepaintBoundary(child: child)); + } + + /// Get the more appointment index(more appointment index map holds more + /// appointment needed cell index and it appointment count) + final List keys = _moreAppointmentIndex.keys.toList(); + + /// Calculate the cell width based on time label width and visible dates + /// count. + final double cellWidth = + (widget.width - widget.timeLabelWidth) / widget.visibleDates.length; + + /// Calculate the maximum appointment width based on cell end padding. + final double maxAppointmentWidth = + cellWidth - widget.calendar.cellEndPadding; + for (int i = 0; i < keys.length; i++) { + final int index = keys[i]; + final DateTime date = widget.visibleDates[index]; + final double xPosition = widget.timeLabelWidth + (index * cellWidth); + final List moreAppointments = []; + final List<_AppointmentView> moreAppointmentViews = + _indexAppointments[index]; + + /// Get the appointments of the more appointment cell index from more + /// appointment views. + for (int j = 0; j < moreAppointmentViews.length; j++) { + final _AppointmentView currentAppointment = moreAppointmentViews[j]; + moreAppointments.add(currentAppointment.appointment); + } + final Widget child = widget.calendar.appointmentBuilder( + context, + CalendarAppointmentDetails( + date: date, + bounds: Rect.fromLTWH( + widget.isRTL + ? widget.width - xPosition - maxAppointmentWidth + : xPosition, + widget.height - _kAllDayAppointmentHeight, + maxAppointmentWidth, + _kAllDayAppointmentHeight - 1), + appointments: + List.unmodifiable(_getCustomAppointments(moreAppointments)), + isMoreAppointmentRegion: true)); + + /// Throw exception when builder return widget is null. + assert(child != null, 'Widget must not be null'); + _children.add(RepaintBoundary(child: child)); + } + } + + return _AllDayAppointmentRenderWidget( + widget.calendar, + widget.view, + widget.visibleDates, + widget.visibleAppointments, + widget.timeLabelWidth, + widget.allDayPainterHeight, + widget.isExpandable, + widget.isExpanding, + widget.isRTL, + widget.calendarTheme, + widget.repaintNotifier, + widget.allDayHoverPosition, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.width, + widget.height, + _appointmentCollection, + _moreAppointmentIndex, + widgets: _children, + ); + } + + void _updateAppointmentDetails() { + _indexAppointments = >{}; + _moreAppointmentIndex = {}; + _appointmentCollection = <_AppointmentView>[]; + + /// Return when the widget as not placed on current visible calendar view. + if (widget.visibleDates != + _updateCalendarStateDetails._currentViewVisibleDates) { + return; + } + + _appointmentCollection = + _updateCalendarStateDetails._allDayAppointmentViewCollection ?? + <_AppointmentView>[]; + final double cellWidth = + (widget.width - widget.timeLabelWidth) / widget.visibleDates.length; + final double cellEndPadding = widget.calendar.cellEndPadding; + const double cornerRadius = (_kAllDayAppointmentHeight * 0.1) > 2 + ? 2 + : _kAllDayAppointmentHeight * 0.1; + + /// Calculate the maximum position of the appointment this widget can hold. + final int position = + widget.allDayPainterHeight ~/ _kAllDayAppointmentHeight; + for (int i = 0; i < _appointmentCollection.length; i++) { + final _AppointmentView appointmentView = _appointmentCollection[i]; + if (appointmentView.canReuse) { + continue; + } + + RRect rect; + if (widget.isRTL) { + rect = RRect.fromRectAndRadius( + Rect.fromLTRB( + ((widget.visibleDates.length - appointmentView.endIndex) * + cellWidth) + + cellEndPadding, + (_kAllDayAppointmentHeight * appointmentView.position) + .toDouble(), + (widget.visibleDates.length - appointmentView.startIndex) * + cellWidth, + ((_kAllDayAppointmentHeight * appointmentView.position) + + _kAllDayAppointmentHeight - + 1) + .toDouble()), + const Radius.circular(cornerRadius)); + } else { + rect = RRect.fromRectAndRadius( + Rect.fromLTRB( + widget.timeLabelWidth + + (appointmentView.startIndex * cellWidth), + (_kAllDayAppointmentHeight * appointmentView.position) + .toDouble(), + (appointmentView.endIndex * cellWidth) + + widget.timeLabelWidth - + cellEndPadding, + ((_kAllDayAppointmentHeight * appointmentView.position) + + _kAllDayAppointmentHeight - + 1) + .toDouble()), + const Radius.circular(cornerRadius)); + } + + for (int j = appointmentView.startIndex; + j < appointmentView.endIndex; + j++) { + List<_AppointmentView> appointmentViews; + if (_indexAppointments.containsKey(j)) { + appointmentViews = _indexAppointments[j]; + appointmentViews.add(appointmentView); + } else { + appointmentViews = <_AppointmentView>[appointmentView]; + } + _indexAppointments[j] = appointmentViews; + } + + /// Calculate the appointment bound for visible region appointments not + /// all visible appointments of the widget. + if (!widget.isRTL && rect.left < widget.timeLabelWidth - 1 || + rect.right > widget.width + 1 || + (rect.bottom > + widget.allDayPainterHeight - _kAllDayAppointmentHeight && + appointmentView.maxPositions > position)) { + continue; + } else if (widget.isRTL && + rect.right > widget.width - widget.timeLabelWidth + 1 || + rect.left < 0 || + (rect.bottom > + widget.allDayPainterHeight - _kAllDayAppointmentHeight && + appointmentView.maxPositions > position)) { + continue; + } + + appointmentView.appointmentRect = rect; + } + + int maxPosition = 0; + 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; + } + + if (maxPosition == -1) { + maxPosition = 0; + } + + /// Calculate the more appointments region when the widget as expandable + /// and its max position greater than widget holding position. + if (widget.isExpandable && maxPosition > position && !widget.isExpanding) { + final List keys = _indexAppointments.keys.toList(); + final int endIndexPosition = position - 1; + for (int i = 0; i < keys.length; i++) { + final int key = keys[i]; + final List<_AppointmentView> appointmentViews = _indexAppointments[key]; + 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; + } + if (maxPosition <= position) { + continue; + } + + for (final _AppointmentView view in appointmentViews) { + if (view.appointment == null) { + continue; + } + + /// Check if position greater than more appointment region index then + /// increase the count, else if the position equal to more appointment + /// region index then check it max position greater than position + /// (because max position value is addition of maximum position + /// with 1). + if (view.position > endIndexPosition || + (view.position == endIndexPosition && + view.maxPositions > position)) { + count++; + } + } + + if (count == 0) { + continue; + } + + _moreAppointmentIndex[key] = count; + } + } + } +} + +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.appointmentCollection, + this.moreAppointmentIndex, + {List widgets}) + : super(children: widgets); + final SfCalendar calendar; + final CalendarView view; + final List visibleDates; + final List visibleAppointments; + final ValueNotifier<_SelectionDetails> repaintNotifier; + final double timeLabelWidth; + final double allDayPainterHeight; + final bool isRTL; + final SfCalendarThemeData calendarTheme; + final ValueNotifier allDayHoverPosition; + final double textScaleFactor; + final bool isMobilePlatform; + final double height; + final double width; + final bool isExpandable; + final bool isExpanding; + final Map moreAppointmentIndex; + final List<_AppointmentView> appointmentCollection; + + @override + _AllDayAppointmentRenderObject createRenderObject(BuildContext context) { + return _AllDayAppointmentRenderObject( + calendar, + view, + visibleDates, + visibleAppointments, + timeLabelWidth, + allDayPainterHeight, + isExpandable, + isExpanding, + isRTL, + calendarTheme, + repaintNotifier, + allDayHoverPosition, + textScaleFactor, + isMobilePlatform, + width, + height, + appointmentCollection, + moreAppointmentIndex); + } + + @override + void updateRenderObject( + BuildContext context, _AllDayAppointmentRenderObject renderObject) { + renderObject + ..appointmentCollection = appointmentCollection + ..moreAppointmentIndex = moreAppointmentIndex + ..calendar = calendar + ..view = view + ..visibleDates = visibleDates + ..visibleAppointments = visibleAppointments + ..timeLabelWidth = timeLabelWidth + ..allDayPainterHeight = allDayPainterHeight + ..isExpandable = isExpandable + ..isExpanding = isExpanding + ..isRTL = isRTL + ..calendarTheme = calendarTheme + ..selectionNotifier = repaintNotifier + ..allDayHoverPosition = allDayHoverPosition + ..textScaleFactor = textScaleFactor + ..isMobilePlatform = isMobilePlatform + ..width = width + ..height = height; + } +} + +class _AllDayAppointmentRenderObject extends RenderBox + with ContainerRenderObjectMixin { + _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.appointmentCollection, + this.moreAppointmentIndex); + + SfCalendar calendar; + bool isMobilePlatform; + bool isExpanding; + Map moreAppointmentIndex; + List<_AppointmentView> appointmentCollection; + + /// Width of the widget. + double _width; + + double get width => _width; + + set width(double value) { + if (_width == value) { + return; + } + + _width = value; + markNeedsLayout(); + } + + /// Total height of the widget. + double _height; + + double get height => _height; + + set height(double value) { + if (_height == value) { + return; + } + + _height = value; + markNeedsLayout(); + } + + /// Current height of the widget. + double _allDayPainterHeight; + + double get allDayPainterHeight => _allDayPainterHeight; + + set allDayPainterHeight(double value) { + if (_allDayPainterHeight == value) { + return; + } + + _allDayPainterHeight = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Current calendar view of the widget. + CalendarView _view; + + CalendarView get view => _view; + + set view(CalendarView value) { + if (_view == value) { + return; + } + + _view = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + double _timeLabelWidth; + + double get timeLabelWidth => _timeLabelWidth; + + set timeLabelWidth(double value) { + if (_timeLabelWidth == value) { + return; + } + + _timeLabelWidth = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Used to check whether the widget is expandable or not. + bool _isExpandable; + + bool get isExpandable => _isExpandable; + + set isExpandable(bool value) { + if (_isExpandable == value) { + return; + } + + _isExpandable = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + double _textScaleFactor; + + double get textScaleFactor => _textScaleFactor; + + set textScaleFactor(double value) { + if (_textScaleFactor == value) { + return; + } + + _textScaleFactor = value; + markNeedsPaint(); + } + + List _visibleAppointments; + + List get visibleAppointments => _visibleAppointments; + + set visibleAppointments(List value) { + if (_visibleAppointments == value) { + return; + } + + _visibleAppointments = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + SfCalendarThemeData _calendarTheme; + + SfCalendarThemeData get calendarTheme => _calendarTheme; + + set calendarTheme(SfCalendarThemeData value) { + if (_calendarTheme == value) { + return; + } + + _calendarTheme = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + List _visibleDates; + + List get visibleDates => _visibleDates; + + set visibleDates(List value) { + if (_visibleDates == value) { + return; + } + + _visibleDates = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + bool _isRTL; + + bool get isRTL => _isRTL; + + set isRTL(bool value) { + if (_isRTL == value) { + return; + } + + _isRTL = value; + markNeedsPaint(); + } + + ValueNotifier _allDayHoverPosition; + + ValueNotifier get allDayHoverPosition => _allDayHoverPosition; + + set allDayHoverPosition(ValueNotifier value) { + if (_allDayHoverPosition == value) { + return; + } + + _allDayHoverPosition?.removeListener(markNeedsPaint); + _allDayHoverPosition = value; + _allDayHoverPosition?.addListener(markNeedsPaint); + } + + ValueNotifier<_SelectionDetails> _selectionNotifier; + + ValueNotifier<_SelectionDetails> get selectionNotifier => _selectionNotifier; + + set selectionNotifier(ValueNotifier<_SelectionDetails> value) { + if (_selectionNotifier == value) { + return; + } + + _selectionNotifier?.removeListener(markNeedsPaint); + _selectionNotifier = value; + _selectionNotifier?.addListener(markNeedsPaint); + } + Paint _rectPainter; TextPainter _textPainter; TextPainter _expanderTextPainter; BoxPainter _boxPainter; - int _maxPosition; bool _isHoveringAppointment = false; - final _UpdateCalendarStateDetails _updateCalendarStateDetails = - _UpdateCalendarStateDetails(); + int _maxPosition = 0; + double _cellWidth = 0; @override - void paint(Canvas canvas, Size size) { - _updateCalendarStateDetails._allDayAppointmentViewCollection = null; - _updateCalendarStateDetails._currentViewVisibleDates = null; - updateCalendarState(_updateCalendarStateDetails); - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - _rectPainter = _rectPainter ?? Paint(); - - _updateCalendarStateDetails._allDayAppointmentViewCollection = - _updateCalendarStateDetails._allDayAppointmentViewCollection ?? - <_AppointmentView>[]; + void setupParentData(RenderObject child) { + if (child.parentData is! _CalendarParentData) { + child.parentData = _CalendarParentData(); + } + } + + /// attach will called when the render object rendered in view. + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _allDayHoverPosition?.addListener(markNeedsPaint); + _selectionNotifier?.addListener(markNeedsPaint); + } + + /// detach will called when the render object removed from view. + @override + void detach() { + _allDayHoverPosition?.removeListener(markNeedsPaint); + _selectionNotifier?.removeListener(markNeedsPaint); + super.detach(); + } + + @override + void performLayout() { + final Size widgetSize = constraints.biggest; + size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height); + RenderBox child = firstChild; + final int position = allDayPainterHeight ~/ _kAllDayAppointmentHeight; + final double maximumBottomPosition = + allDayPainterHeight - _kAllDayAppointmentHeight; + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView _appointmentView = appointmentCollection[i]; + if (_appointmentView.appointment == null || + child == null || + _appointmentView.appointmentRect == null) { + continue; + } + + if (!isRTL && + _appointmentView.appointmentRect.left < timeLabelWidth - 1 || + _appointmentView.appointmentRect.right > size.width + 1 || + (_appointmentView.appointmentRect.bottom > maximumBottomPosition && + _appointmentView.maxPositions > position)) { + child = childAfter(child); + continue; + } else if (isRTL && + _appointmentView.appointmentRect.right > + size.width - timeLabelWidth + 1 || + _appointmentView.appointmentRect.left < 0 || + (_appointmentView.appointmentRect.bottom > maximumBottomPosition && + _appointmentView.maxPositions > position)) { + child = childAfter(child); + continue; + } + child.layout(constraints.copyWith( + minHeight: _appointmentView.appointmentRect.height, + maxHeight: _appointmentView.appointmentRect.height, + minWidth: _appointmentView.appointmentRect.width, + maxWidth: _appointmentView.appointmentRect.width)); + child = childAfter(child); + } + + _cellWidth = (size.width - timeLabelWidth) / visibleDates.length; + final double appointmentHeight = _kAllDayAppointmentHeight - 1; + final double maxAppointmentWidth = _cellWidth - calendar.cellEndPadding; + final List keys = moreAppointmentIndex.keys.toList(); + for (int i = 0; i < keys.length; i++) { + if (child == null) { + continue; + } + + child.layout(constraints.copyWith( + minHeight: appointmentHeight, + maxHeight: appointmentHeight, + minWidth: maxAppointmentWidth, + maxWidth: maxAppointmentWidth)); + child = childAfter(child); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + _rectPainter ??= Paint(); + final Canvas canvas = context.canvas; if (view == CalendarView.day) { _rectPainter ??= Paint(); _rectPainter.strokeWidth = 0.5; @@ -87,18 +812,13 @@ class _AllDayAppointmentPainter extends CustomPainter { _rectPainter); } - if (visibleDates != _updateCalendarStateDetails._currentViewVisibleDates) { - return; - } - _rectPainter.isAntiAlias = true; _cellWidth = (size.width - timeLabelWidth) / visibleDates.length; const double textPadding = 3; + final double cellEndPadding = calendar.cellEndPadding; _maxPosition = 0; - if (_updateCalendarStateDetails - ._allDayAppointmentViewCollection.isNotEmpty) { - _maxPosition = _updateCalendarStateDetails - ._allDayAppointmentViewCollection + if (appointmentCollection.isNotEmpty) { + _maxPosition = appointmentCollection .reduce( (_AppointmentView currentAppView, _AppointmentView nextAppView) => currentAppView.maxPositions > nextAppView.maxPositions @@ -111,144 +831,125 @@ class _AllDayAppointmentPainter extends CustomPainter { _maxPosition = 0; } + _isHoveringAppointment = false; final int position = allDayPainterHeight ~/ _kAllDayAppointmentHeight; - for (int i = 0; - i < _updateCalendarStateDetails._allDayAppointmentViewCollection.length; - i++) { - final _AppointmentView appointmentView = - _updateCalendarStateDetails._allDayAppointmentViewCollection[i]; - if (appointmentView.canReuse) { + RenderBox child = firstChild; + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView appointmentView = appointmentCollection[i]; + if (appointmentView.canReuse || + appointmentView.appointmentRect == null || + appointmentView.appointment == null) { continue; } - final Appointment appointment = appointmentView.appointment; - RRect rect; - if (isRTL) { - rect = RRect.fromRectAndRadius( - Rect.fromLTRB( - ((visibleDates.length - appointmentView.endIndex) * - _cellWidth) + - textPadding, - (_kAllDayAppointmentHeight * appointmentView.position) - .toDouble(), - (visibleDates.length - appointmentView.startIndex) * _cellWidth, - ((_kAllDayAppointmentHeight * appointmentView.position) + - _kAllDayAppointmentHeight - - 1) - .toDouble()), - const Radius.circular((_kAllDayAppointmentHeight * 0.1) > 2 - ? 2 - : (_kAllDayAppointmentHeight * 0.1))); - } else { - rect = RRect.fromRectAndRadius( - Rect.fromLTRB( - timeLabelWidth + (appointmentView.startIndex * _cellWidth), - (_kAllDayAppointmentHeight * appointmentView.position) - .toDouble(), - (appointmentView.endIndex * _cellWidth) + - timeLabelWidth - - textPadding, - ((_kAllDayAppointmentHeight * appointmentView.position) + - _kAllDayAppointmentHeight - - 1) - .toDouble()), - const Radius.circular((_kAllDayAppointmentHeight * 0.1) > 2 - ? 2 - : (_kAllDayAppointmentHeight * 0.1))); - } - - appointmentView.appointmentRect = rect; + final RRect rect = appointmentView.appointmentRect; if (!isRTL && rect.left < timeLabelWidth - 1 || rect.right > size.width + 1 || (rect.bottom > allDayPainterHeight - _kAllDayAppointmentHeight && appointmentView.maxPositions > position)) { + if (child != null) { + child = childAfter(child); + } continue; } else if (isRTL && rect.right > size.width - timeLabelWidth + 1 || rect.left < 0 || (rect.bottom > allDayPainterHeight - _kAllDayAppointmentHeight && appointmentView.maxPositions > position)) { + if (child != null) { + child = childAfter(child); + } continue; } - _rectPainter.color = appointment.color; - canvas.drawRRect(rect, _rectPainter); - final TextSpan span = TextSpan( - text: _getAllDayAppointmentText(appointment), - style: calendar.appointmentTextStyle, - ); - _textPainter = _textPainter ?? - TextPainter( - textDirection: TextDirection.ltr, - maxLines: 1, - textAlign: TextAlign.left, - textScaleFactor: textScaleFactor, - textWidthBasis: TextWidthBasis.longestLine); - _textPainter.text = span; - _textPainter.layout( - minWidth: 0, - maxWidth: - rect.width - textPadding >= 0 ? rect.width - textPadding : 0); - if (_textPainter.maxLines == 1 && _textPainter.height > rect.height) { - continue; - } - - final bool canAddSpanIcon = - _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 = _convertToStartTime(visibleDates[0]); - final DateTime viewEndDate = - _convertToEndTime(visibleDates[visibleDates.length - 1]); - double iconSize = _getTextSize( - rect, - (calendar.appointmentTextStyle.fontSize * - _textPainter.textScaleFactor)); - if (_canAddForwardSpanIcon( - appStartTime, appEndTime, viewStartDate, viewEndDate)) { - canAddForwardIcon = true; - iconSize = null; - } else if (_canAddBackwardSpanIcon( - appStartTime, appEndTime, viewStartDate, viewEndDate)) { - canAddBackwardIcon = true; - } else { - canAddForwardIcon = true; - canAddBackwardIcon = true; + if (child != null) { + child.paint(context, Offset(rect.left, rect.top)); + child = childAfter(child); + } else { + final Appointment appointment = appointmentView.appointment; + _rectPainter.color = appointment.color; + canvas.drawRRect(rect, _rectPainter); + final TextSpan span = TextSpan( + text: _getAllDayAppointmentText(appointment), + style: calendar.appointmentTextStyle, + ); + _textPainter = _textPainter ?? + TextPainter( + textDirection: TextDirection.ltr, + maxLines: 1, + textAlign: TextAlign.left, + textScaleFactor: textScaleFactor, + textWidthBasis: TextWidthBasis.longestLine); + _textPainter.text = span; + _textPainter.layout( + minWidth: 0, + maxWidth: + rect.width - textPadding >= 0 ? rect.width - textPadding : 0); + if (_textPainter.maxLines == 1 && _textPainter.height > rect.height) { + continue; } - if (iconSize != null) { - if (isRTL) { - xPosition -= iconSize + (kIsWeb ? 2 : 0); + final bool canAddSpanIcon = + _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 = _convertToStartTime(visibleDates[0]); + final DateTime viewEndDate = + _convertToEndTime(visibleDates[visibleDates.length - 1]); + double iconSize = _getTextSize( + rect, + (calendar.appointmentTextStyle.fontSize * + _textPainter.textScaleFactor)); + if (_canAddForwardSpanIcon( + appStartTime, appEndTime, viewStartDate, viewEndDate)) { + canAddForwardIcon = true; + iconSize = null; + } else if (_canAddBackwardSpanIcon( + appStartTime, appEndTime, viewStartDate, viewEndDate)) { + canAddBackwardIcon = true; } else { - xPosition += iconSize + (kIsWeb ? 2 : 0); + canAddForwardIcon = true; + canAddBackwardIcon = true; + } + + if (iconSize != null) { + if (isRTL) { + xPosition -= iconSize + (isMobilePlatform ? 0 : 2); + } else { + xPosition += iconSize + (isMobilePlatform ? 0 : 2); + } } } - } - _textPainter.paint( - canvas, - Offset( - xPosition, rect.top + (rect.height - _textPainter.height) / 2)); - if (appointment.recurrenceRule != null && - appointment.recurrenceRule.isNotEmpty) { - _addRecurrenceIcon(canvas, rect, textPadding); - } + _textPainter.paint( + canvas, + Offset( + xPosition, rect.top + (rect.height - _textPainter.height) / 2)); + if (appointment.recurrenceRule != null && + appointment.recurrenceRule.isNotEmpty) { + _addRecurrenceIcon(canvas, rect, textPadding); + } - if (canAddSpanIcon) { - if (canAddForwardIcon && canAddBackwardIcon) { - _addForwardSpanIconForAllDay(canvas, rect, textPadding); - _addBackwardSpanIconForAllDay(canvas, rect, textPadding); - } else if (canAddBackwardIcon) { - _addBackwardSpanIconForAllDay(canvas, rect, textPadding); - } else { - _addForwardSpanIconForAllDay(canvas, rect, textPadding); + if (canAddSpanIcon) { + if (canAddForwardIcon && canAddBackwardIcon) { + _addForwardSpanIconForAllDay( + canvas, rect, textPadding, isMobilePlatform); + _addBackwardSpanIconForAllDay( + canvas, rect, textPadding, isMobilePlatform); + } else if (canAddBackwardIcon) { + _addBackwardSpanIconForAllDay( + canvas, rect, textPadding, isMobilePlatform); + } else { + _addForwardSpanIconForAllDay( + canvas, rect, textPadding, isMobilePlatform); + } } } @@ -256,22 +957,40 @@ class _AllDayAppointmentPainter extends CustomPainter { _addMouseHoveringForAppointment(canvas, rect); } - if (repaintNotifier.value != null && - repaintNotifier.value.appointmentView != null && - repaintNotifier.value.appointmentView.appointment != null && - repaintNotifier.value.appointmentView.appointment == + if (selectionNotifier.value != null && + selectionNotifier.value.appointmentView != null && + selectionNotifier.value.appointmentView.appointment != null && + selectionNotifier.value.appointmentView.appointment == appointmentView.appointment) { _addSelectionForAppointment(canvas, appointmentView); } } - if (repaintNotifier.value != null && - repaintNotifier.value.selectedDate != null) { - _addSelectionForAllDayPanel(canvas, size, textPadding); + if (selectionNotifier.value != null && + selectionNotifier.value.selectedDate != null) { + _addSelectionForAllDayPanel(canvas, size, cellEndPadding); } if (isExpandable && _maxPosition > position && !isExpanding) { - _addExpanderText(canvas, position, textPadding); + if (child != null) { + final double endYPosition = + allDayPainterHeight - _kAllDayAppointmentHeight; + 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); + child.paint(context, Offset(xPosition, endYPosition)); + child = childAfter(child); + } + } else { + _addExpanderText(canvas, position, textPadding); + } } if (isExpandable) { @@ -304,12 +1023,12 @@ class _AllDayAppointmentPainter extends CustomPainter { } void _addForwardSpanIconForAllDay( - Canvas canvas, RRect rect, double textPadding) { + Canvas canvas, RRect rect, double textPadding, bool isMobilePlatform) { final double textSize = _getTextSize(rect, calendar.appointmentTextStyle.fontSize); final TextSpan icon = _getSpanIcon( calendar.appointmentTextStyle.color, textSize, isRTL ? false : true); - final double leftPadding = kIsWeb ? 2 : 1; + final double leftPadding = isMobilePlatform ? 1 : 2; _textPainter.text = icon; _textPainter.layout( minWidth: 0, @@ -334,12 +1053,12 @@ class _AllDayAppointmentPainter extends CustomPainter { } void _addBackwardSpanIconForAllDay( - Canvas canvas, RRect rect, double textPadding) { + Canvas canvas, RRect rect, double textPadding, bool isMobilePlatform) { final double textSize = _getTextSize(rect, calendar.appointmentTextStyle.fontSize); final TextSpan icon = _getSpanIcon( calendar.appointmentTextStyle.color, textSize, isRTL ? true : false); - final double leftPadding = kIsWeb ? 2 : 1; + final double leftPadding = isMobilePlatform ? 1 : 2; _textPainter.text = icon; _textPainter.layout( minWidth: 0, @@ -375,40 +1094,10 @@ class _AllDayAppointmentPainter extends CustomPainter { textWidthBasis: TextWidthBasis.longestLine); final double endYPosition = allDayPainterHeight - _kAllDayAppointmentHeight; - final int endIndexPosition = position - 1; - for (int i = 0; i < visibleDates.length; i++) { - int count = 0; - for (final _AppointmentView view - in _updateCalendarStateDetails._allDayAppointmentViewCollection) { - if (view.appointment == null) { - continue; - } - - /// Check appointment after the all day panel height. - /// start index is used to specify current visible date index and - /// end index used to specify the next visible date index - /// eg., if appointment start and end date as same then the - /// end index is point to next index of start index. start index as - /// 5 then end index as 6 when the start and end date as equal. - if (view.startIndex <= i && view.endIndex > i) { - /// Add after the end position appointment and same position - /// but its max position greater than end position(add appointment - /// if appointment position as end position and the visible date - /// cell have more appointments). - if (view.position > endIndexPosition || - (view.position == endIndexPosition && - view.maxPositions > position)) { - count++; - } - } - } - - if (count == 0) { - continue; - } - + final List keys = moreAppointmentIndex.keys.toList(); + for (final int index in keys) { final TextSpan span = TextSpan( - text: '+ ' + count.toString(), + text: '+ ' + moreAppointmentIndex[index].toString(), style: textStyle, ); _textPainter.text = span; @@ -421,10 +1110,10 @@ class _AllDayAppointmentPainter extends CustomPainter { canvas, Offset( isRTL - ? ((visibleDates.length - i) * _cellWidth) - + ? ((visibleDates.length - index) * _cellWidth) - _textPainter.width - textPadding - : timeLabelWidth + (i * _cellWidth) + textPadding, + : timeLabelWidth + (index * _cellWidth) + textPadding, endYPosition + ((_kAllDayAppointmentHeight - _textPainter.height) / 2))); } @@ -469,29 +1158,30 @@ class _AllDayAppointmentPainter extends CustomPainter { } void _addMouseHoveringForAllDayPanel(Canvas canvas, Size size) { - final int rowIndex = allDayHoverPosition.dx ~/ _cellWidth; - final double leftPosition = rowIndex * _cellWidth; + if (allDayHoverPosition == null || allDayHoverPosition.value == null) { + return; + } + final int rowIndex = + (allDayHoverPosition.value.dx - (isRTL ? 0 : timeLabelWidth)) ~/ + _cellWidth; + final double leftPosition = + (rowIndex * _cellWidth) + (isRTL ? 0 : timeLabelWidth); _rectPainter.color = Colors.grey.withOpacity(0.1); canvas.drawRect( - Rect.fromLTWH(isRTL ? leftPosition : leftPosition + timeLabelWidth, 0, - _cellWidth, size.height), - _rectPainter); + Rect.fromLTWH(leftPosition, 0, _cellWidth, size.height), _rectPainter); } void _addSelectionForAllDayPanel( - Canvas canvas, Size size, double textPadding) { + Canvas canvas, Size size, double appointmentEndPadding) { final int index = - _getIndex(visibleDates, repaintNotifier.value.selectedDate); + _getIndex(visibleDates, selectionNotifier.value.selectedDate); Decoration selectionDecoration = calendar.selectionDecoration; /// Set the default selection decoration background color with opacity /// value based on theme brightness when selected date hold all day /// appointment. - for (int i = 0; - i < _updateCalendarStateDetails._allDayAppointmentViewCollection.length; - i++) { - final _AppointmentView appointmentView = - _updateCalendarStateDetails._allDayAppointmentViewCollection[i]; + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.position == 0 && appointmentView.startIndex <= index && appointmentView.endIndex > index) { @@ -523,20 +1213,32 @@ class _AllDayAppointmentPainter extends CustomPainter { double xValue = timeLabelWidth + (index * _cellWidth); if (isRTL) { xValue = size.width - xValue - _cellWidth; - rect = Rect.fromLTRB(xValue + textPadding, 0, xValue + _cellWidth, - _kAllDayAppointmentHeight - 1); + rect = Rect.fromLTRB(xValue + appointmentEndPadding, 0, + xValue + _cellWidth, _kAllDayAppointmentHeight - 1); } else { - rect = Rect.fromLTRB(xValue, 0, xValue + _cellWidth - textPadding, + rect = Rect.fromLTRB( + xValue, + 0, + xValue + _cellWidth - appointmentEndPadding, _kAllDayAppointmentHeight - 1); } - _boxPainter = selectionDecoration.createBoxPainter(); + _boxPainter = + selectionDecoration.createBoxPainter(_updateSelectionDecorationPainter); _boxPainter.paint(canvas, Offset(rect.left, rect.top), ImageConfiguration(size: rect.size)); selectionDecoration = null; } + /// 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); + } + void _addSelectionForAppointment( Canvas canvas, _AppointmentView appointmentView) { Decoration selectionDecoration = calendar.selectionDecoration; @@ -549,18 +1251,22 @@ class _AllDayAppointmentPainter extends CustomPainter { Rect rect = appointmentView.appointmentRect.outerRect; rect = Rect.fromLTRB(rect.left, rect.top, rect.right, rect.bottom); - _boxPainter = selectionDecoration.createBoxPainter(); + _boxPainter = + selectionDecoration.createBoxPainter(_updateSelectionDecorationPainter); _boxPainter.paint(canvas, Offset(rect.left, rect.top), ImageConfiguration(size: rect.size)); } void _addMouseHoveringForAppointment(Canvas canvas, RRect rect) { _rectPainter ??= Paint(); - _isHoveringAppointment = false; - if (rect.left < allDayHoverPosition.dx && - rect.right - timeLabelWidth > allDayHoverPosition.dx && - rect.top < allDayHoverPosition.dy && - rect.bottom > allDayHoverPosition.dy) { + if (allDayHoverPosition == null || allDayHoverPosition.value == null) { + return; + } + + if (rect.left < allDayHoverPosition.value.dx && + rect.right > allDayHoverPosition.value.dx && + rect.top < allDayHoverPosition.value.dy && + rect.bottom > allDayHoverPosition.value.dy) { _rectPainter.color = calendarTheme.selectionBorderColor.withOpacity(0.4); _rectPainter.strokeWidth = 2; _rectPainter.style = PaintingStyle.stroke; @@ -592,44 +1298,63 @@ class _AllDayAppointmentPainter extends CustomPainter { } @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _AllDayAppointmentPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.allDayPainterHeight != allDayPainterHeight || - oldWidget.visibleAppointment != visibleAppointment || - oldWidget.calendar.cellBorderColor != calendar.cellBorderColor || - oldWidget.calendarTheme != calendarTheme || - oldWidget.isRTL != isRTL || - oldWidget.view != view || - oldWidget.isExpandable != isExpandable || - oldWidget.allDayHoverPosition != allDayHoverPosition || - oldWidget.textScaleFactor != textScaleFactor; - } - - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the list - /// of custom painter semantics which contains the rect area and the semantics - /// properties for accessibility + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config.isSemanticBoundary = true; + } + @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilder(size); - }; + void assembleSemanticsNode( + SemanticsNode node, + SemanticsConfiguration config, + Iterable children, + ) { + final List semantics = _getSemanticsBuilder(size); + final List semanticsNodes = []; + for (int i = 0; i < semantics.length; i++) { + final CustomPainterSemantics currentSemantics = semantics[i]; + final SemanticsNode newChild = SemanticsNode( + key: currentSemantics.key, + ); + + final SemanticsProperties properties = currentSemantics.properties; + final SemanticsConfiguration config = SemanticsConfiguration(); + if (properties.label != null) { + config.label = properties.label; + } + if (properties.textDirection != null) { + config.textDirection = properties.textDirection; + } + + newChild.updateWith( + config: config, + // As of now CustomPainter does not support multiple tree levels. + childrenInInversePaintOrder: const [], + ); + + newChild + ..rect = currentSemantics.rect + ..transform = currentSemantics.transform + ..tags = currentSemantics.tags; + + semanticsNodes.add(newChild); + } + + final List finalChildren = []; + finalChildren.addAll(semanticsNodes); + finalChildren.addAll(children); + + super.assembleSemanticsNode(node, config, finalChildren); } @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _AllDayAppointmentPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.visibleAppointment != visibleAppointment || - oldWidget.allDayPainterHeight != allDayPainterHeight; + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + return; } List _getSemanticsBuilder(Size size) { final List semanticsBuilder = []; - final List<_AppointmentView> appointmentCollection = - _updateCalendarStateDetails._allDayAppointmentViewCollection; if (appointmentCollection == null || appointmentCollection.isEmpty) { return semanticsBuilder; } @@ -655,48 +1380,18 @@ class _AllDayAppointmentPainter extends CustomPainter { if (isExpandable && _maxPosition > (allDayPainterHeight ~/ _kAllDayAppointmentHeight) && !isExpanding) { - final int endIndexPosition = position - 1; - for (int i = 0; i < visibleDates.length; i++) { - int count = 0; - for (final _AppointmentView view - in _updateCalendarStateDetails._allDayAppointmentViewCollection) { - if (view.appointment == null) { - continue; - } - - /// Check appointment after the all day panel height. - /// start index is used to specify current visible date index and - /// end index used to specify the next visible date index - /// eg., if appointment start and end date as same then the - /// end index is point to next index of start index. start index as - /// 5 then end index as 6 when the start and end date as equal. - if (view.startIndex <= i && view.endIndex > i) { - /// Add after the end position appointment and same position - /// but its max position greater than end position(add appointment - /// if appointment position as end position and the visible date - /// cell have more appointments). - if (view.position > endIndexPosition || - (view.position == endIndexPosition && - view.maxPositions > position)) { - count++; - } - } - } - - if (count == 0) { - continue; - } - + final List keys = moreAppointmentIndex.keys.toList(); + for (final int index in keys) { semanticsBuilder.add(CustomPainterSemantics( rect: Rect.fromLTWH( isRTL - ? ((visibleDates.length - i) * _cellWidth) - _cellWidth - : timeLabelWidth + (i * _cellWidth), + ? ((visibleDates.length - index) * _cellWidth) - _cellWidth + : timeLabelWidth + (index * _cellWidth), bottom, _cellWidth, _kAllDayAppointmentHeight), properties: SemanticsProperties( - label: '+' + count.toString(), + label: '+' + moreAppointmentIndex[index].toString(), textDirection: TextDirection.ltr, ), )); @@ -706,6 +1401,7 @@ class _AllDayAppointmentPainter extends CustomPainter { for (int i = 0; i < appointmentCollection.length; i++) { final _AppointmentView view = appointmentCollection[i]; if (view.appointment == null || + view.appointmentRect == null || (view.appointmentRect != null && view.appointmentRect.bottom > bottom && view.maxPositions > position)) { @@ -713,9 +1409,7 @@ class _AllDayAppointmentPainter extends CustomPainter { } semanticsBuilder.add(CustomPainterSemantics( - rect: view.appointmentRect == null - ? const Rect.fromLTWH(0, 0, 10, 10) - : view.appointmentRect?.outerRect, + rect: view.appointmentRect?.outerRect, properties: SemanticsProperties( label: _getAppointmentText(view.appointment), textDirection: TextDirection.ltr, diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/appointment_layout.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/appointment_layout.dart index eaf999d68..8ca836f68 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 @@ -12,60 +12,1331 @@ class _AppointmentView { int resourceIndex = -1; } -class _AppointmentPainter extends CustomPainter { - _AppointmentPainter( +TextPainter _updateTextPainter(TextSpan span, TextPainter textPainter, + bool isRTL, double textScaleFactor) { + textPainter = textPainter ?? TextPainter(); + textPainter.text = span; + textPainter.maxLines = 1; + textPainter.textDirection = TextDirection.ltr; + textPainter.textAlign = isRTL ? TextAlign.right : TextAlign.left; + textPainter.textWidthBasis = TextWidthBasis.longestLine; + textPainter.textScaleFactor = textScaleFactor; + return textPainter; +} + +class _AppointmentLayout extends StatefulWidget { + _AppointmentLayout( this.calendar, this.view, this.visibleDates, this.visibleAppointments, this.timeIntervalHeight, - this.repaintNotifier, this.calendarTheme, this.isRTL, this.appointmentHoverPosition, this.resourceCollection, this.resourceItemHeight, this.textScaleFactor, - {this.updateCalendarState}) - : super(repaint: repaintNotifier); - - SfCalendar calendar; + this.isMobilePlatform, + this.width, + this.height, + this.updateCalendarState, + {Key key}) + : super(key: key); + + final SfCalendar calendar; final CalendarView view; final List visibleDates; final double timeIntervalHeight; final _UpdateCalendarState updateCalendarState; final bool isRTL; final SfCalendarThemeData calendarTheme; - final Offset appointmentHoverPosition; - final ValueNotifier repaintNotifier; + final ValueNotifier appointmentHoverPosition; final List resourceCollection; final double resourceItemHeight; final double textScaleFactor; + final bool isMobilePlatform; + final double width; + final double height; + final ValueNotifier> visibleAppointments; + + @override + _AppointmentLayoutState createState() => _AppointmentLayoutState(); +} + +class _AppointmentLayoutState extends State<_AppointmentLayout> { + /// It holds the appointment views for the visible appointments. + List<_AppointmentView> _appointmentCollection; + + /// It holds the appointment list based on its visible index value. + Map> _indexAppointments; + + /// It holds the more appointment index appointment counts based on its index. + Map _monthAppointmentCountViews; + + /// It holds the children of the widget, it holds null or empty when + /// appointment builder is null. + List _children; + + final _UpdateCalendarStateDetails _updateCalendarStateDetails = + _UpdateCalendarStateDetails(); + TextPainter _textPainter; + + @override + void initState() { + _indexAppointments = >{}; + _appointmentCollection = <_AppointmentView>[]; + _monthAppointmentCountViews = {}; + widget.updateCalendarState(_updateCalendarStateDetails); + _textPainter = TextPainter(); + _children = []; + _updateAppointmentDetails(); + widget.visibleAppointments?.addListener(_updateVisibleAppointment); + super.initState(); + } + + @override + void didUpdateWidget(_AppointmentLayout oldWidget) { + bool isAppointmentDetailsUpdated = false; + if (widget.visibleDates != oldWidget.visibleDates || + widget.timeIntervalHeight != oldWidget.timeIntervalHeight || + widget.calendar != oldWidget.calendar || + widget.width != oldWidget.width || + widget.height != oldWidget.height || + (_isTimelineView(widget.view) && + (widget.resourceCollection != oldWidget.resourceCollection || + widget.resourceItemHeight != oldWidget.resourceItemHeight))) { + isAppointmentDetailsUpdated = true; + _updateAppointmentDetails(); + } + + if (widget.visibleAppointments != oldWidget.visibleAppointments) { + oldWidget.visibleAppointments?.removeListener(_updateVisibleAppointment); + widget.visibleAppointments?.addListener(_updateVisibleAppointment); + if (!_isCollectionEqual(widget.visibleAppointments.value, + oldWidget.visibleAppointments.value) && + !isAppointmentDetailsUpdated) { + _updateAppointmentDetails(); + } + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + widget.visibleAppointments?.removeListener(_updateVisibleAppointment); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + _children ??= []; + + /// Create the widgets when appointment builder is not null. + if (_children.isEmpty && + _appointmentCollection != null && + widget.calendar.appointmentBuilder != null) { + for (int i = 0; i < _appointmentCollection.length; i++) { + final _AppointmentView appointmentView = _appointmentCollection[i]; + + /// Check the appointment view have appointment, if not then the + /// appointment view is not valid or it will be used for reusing view. + if (appointmentView.appointment == null || + appointmentView.appointmentRect == null) { + continue; + } + + final DateTime date = DateTime( + appointmentView.appointment._actualStartTime.year, + appointmentView.appointment._actualStartTime.month, + appointmentView.appointment._actualStartTime.day); + final Widget child = widget.calendar.appointmentBuilder( + context, + CalendarAppointmentDetails( + date: date, + bounds: Rect.fromLTWH( + appointmentView.appointmentRect.left, + appointmentView.appointmentRect.top, + appointmentView.appointmentRect.width, + appointmentView.appointmentRect.height), + appointments: List.unmodifiable([ + appointmentView.appointment._data ?? + appointmentView.appointment + ]), + isMoreAppointmentRegion: false)); + + /// Throw exception when builder return widget is null. + assert(child != null, 'Widget must not be null'); + _children.add(RepaintBoundary(child: child)); + } + + if (_monthAppointmentCountViews != null) { + final List keys = _monthAppointmentCountViews.keys.toList(); + + /// Get the more appointment index(more appointment index map holds more + /// appointment needed cell index and it bound) + for (int i = 0; i < keys.length; i++) { + final int index = keys[i]; + final List moreAppointments = []; + final List<_AppointmentView> moreAppointmentViews = + _indexAppointments[index]; + + /// Get the appointments of the more appointment cell index from more + /// appointment views. + for (int j = 0; j < moreAppointmentViews.length; j++) { + final _AppointmentView currentAppointment = moreAppointmentViews[j]; + moreAppointments.add(currentAppointment.appointment); + } + + final DateTime date = widget.visibleDates[index]; + final RRect moreRegionRect = _monthAppointmentCountViews[index]; + final Widget child = widget.calendar.appointmentBuilder( + context, + CalendarAppointmentDetails( + date: date, + bounds: Rect.fromLTWH(moreRegionRect.left, moreRegionRect.top, + moreRegionRect.width, moreRegionRect.height), + appointments: List.unmodifiable( + _getCustomAppointments(moreAppointments)), + isMoreAppointmentRegion: true)); + + /// Throw exception when builder return widget is null. + assert(child != null, 'Widget must not be null'); + _children.add(RepaintBoundary(child: child)); + } + } + } + + 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, + _appointmentCollection, + _indexAppointments, + _monthAppointmentCountViews, + widgets: _children); + } + + _AppointmentView _getAppointmentViewOnPoint(double x, double y) { + if (_appointmentCollection == null) { + return null; + } + + _AppointmentView selectedAppointmentView; + for (int i = 0; i < _appointmentCollection.length; i++) { + final _AppointmentView appointmentView = _appointmentCollection[i]; + if (appointmentView.appointment != null && + appointmentView.appointmentRect != null && + appointmentView.appointmentRect.left <= x && + appointmentView.appointmentRect.right >= x && + appointmentView.appointmentRect.top <= y && + appointmentView.appointmentRect.bottom >= y) { + selectedAppointmentView = appointmentView; + break; + } + } + + if (selectedAppointmentView == null && + _monthAppointmentCountViews != null && + widget.view == CalendarView.month && + widget.calendar.monthViewSettings.appointmentDisplayMode == + MonthAppointmentDisplayMode.appointment) { + final List keys = _monthAppointmentCountViews.keys.toList(); + for (int i = 0; i < keys.length; i++) { + final RRect rect = _monthAppointmentCountViews[keys[i]]; + + if (rect != null && + rect.left <= x && + rect.right >= x && + rect.top <= y && + rect.bottom >= y) { + selectedAppointmentView = _AppointmentView() + ..appointment = Appointment() + ..appointmentRect = rect; + break; + } + } + } + + return selectedAppointmentView; + } + + void _updateVisibleAppointment() { + widget.updateCalendarState(_updateCalendarStateDetails); + if (!mounted) { + return; + } + + setState(() { + _updateAppointmentDetails(); + }); + } + + void _updateAppointmentDetails() { + _monthAppointmentCountViews = {}; + _indexAppointments = >{}; + widget.updateCalendarState(_updateCalendarStateDetails); + _appointmentCollection ??= <_AppointmentView>[]; + _resetAppointmentView(_appointmentCollection); + _children.clear(); + if (widget.visibleDates != + _updateCalendarStateDetails._currentViewVisibleDates) { + return; + } + + final List visibleAppointments = + widget.visibleAppointments.value; + switch (widget.view) { + case CalendarView.month: + { + _updateMonthAppointmentDetails(visibleAppointments); + } + break; + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + { + _updateDayAppointmentDetails(visibleAppointments); + } + break; + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + { + _updateTimelineAppointmentDetails(visibleAppointments); + } + break; + case CalendarView.schedule: + return; + } + } + + void _updateMonthAppointmentDetails(List visibleAppointments) { + final double cellWidth = widget.width / _kNumberOfDaysInWeek; + final double cellHeight = + widget.height / widget.calendar.monthViewSettings.numberOfWeeksInView; + if (widget.calendar.monthCellBuilder != null || + widget.calendar.monthViewSettings.appointmentDisplayMode != + MonthAppointmentDisplayMode.appointment) { + return; + } + + double xPosition = 0; + double yPosition = 0; + final int count = widget.visibleDates.length; + DateTime visibleStartDate = _convertToStartTime(widget.visibleDates[0]); + DateTime visibleEndDate = _convertToEndTime(widget.visibleDates[count - 1]); + int visibleStartIndex = 0; + int visibleEndIndex = + (widget.calendar.monthViewSettings.numberOfWeeksInView * + _kNumberOfDaysInWeek) - + 1; + final bool showTrailingLeadingDates = _isLeadingAndTrailingDatesVisible( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates); + if (!showTrailingLeadingDates) { + final DateTime currentMonthDate = widget.visibleDates[count ~/ 2]; + visibleStartDate = + _convertToStartTime(_getMonthStartDate(currentMonthDate)); + visibleEndDate = _convertToEndTime(_getMonthEndDate(currentMonthDate)); + visibleStartIndex = _getIndex(widget.visibleDates, visibleStartDate); + visibleEndIndex = _getIndex(widget.visibleDates, visibleEndDate); + } + + _updateAppointment( + 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); + _textPainter = _updateTextPainter( + dateText, _textPainter, widget.isRTL, widget.textScaleFactor); + + /// cell padding and start position calculated by month date cell + /// rendering padding and size. + /// Cell padding value includes month cell text top padding(5) and circle + /// top(4) and bottom(4) padding + const double cellPadding = 13; + + /// Today circle radius as circle radius added after the text height. + const double todayCircleRadius = 5; + final double startPosition = + cellPadding + _textPainter.preferredLineHeight + todayCircleRadius; + final int maximumDisplayCount = + widget.calendar.monthViewSettings.appointmentDisplayCount ?? 3; + final double appointmentHeight = + (cellHeight - startPosition) / maximumDisplayCount; + // right side padding used to add padding on appointment view right side + // in month view + final double cellEndPadding = widget.calendar.cellEndPadding; + for (int i = 0; i < _appointmentCollection.length; i++) { + final _AppointmentView appointmentView = _appointmentCollection[i]; + if (appointmentView.canReuse || appointmentView.appointment == null) { + continue; + } + + if (appointmentView.position < maximumDisplayCount || + (appointmentView.position == maximumDisplayCount && + appointmentView.maxPositions == maximumDisplayCount)) { + final double appointmentWidth = + (appointmentView.endIndex - appointmentView.startIndex + 1) * + cellWidth; + + if (widget.isRTL) { + xPosition = + (6 - (appointmentView.startIndex % _kNumberOfDaysInWeek)) * + cellWidth; + xPosition -= appointmentWidth - cellWidth; + } else { + xPosition = + (appointmentView.startIndex % _kNumberOfDaysInWeek) * cellWidth; + } + + yPosition = + (appointmentView.startIndex ~/ _kNumberOfDaysInWeek) * cellHeight; + if (appointmentView.position <= maximumDisplayCount) { + yPosition = yPosition + + startPosition + + (appointmentHeight * (appointmentView.position - 1)); + } else { + yPosition = yPosition + + startPosition + + (appointmentHeight * (maximumDisplayCount - 1)); + } + + final Radius cornerRadius = Radius.circular( + (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), + cornerRadius); + + appointmentView.appointmentRect = rect; + } + } + + 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; + if (maxPosition <= maximumDisplayCount) { + continue; + } + if (widget.isRTL) { + xPosition = (6 - (index % _kNumberOfDaysInWeek)) * cellWidth; + } else { + xPosition = (index % _kNumberOfDaysInWeek) * cellWidth; + } + + yPosition = ((index ~/ _kNumberOfDaysInWeek) * cellHeight) + + cellHeight - + appointmentHeight; + + final RRect moreRegionRect = RRect.fromRectAndRadius( + Rect.fromLTWH( + widget.isRTL ? xPosition + cellEndPadding : xPosition, + yPosition, + cellWidth - cellEndPadding > 0 ? cellWidth - cellEndPadding : 0, + appointmentHeight - 1), + const Radius.circular(0)); + + _monthAppointmentCountViews[index] = moreRegionRect; + } + } + + void _updateDayAppointmentDetails(List visibleAppointments) { + final double timeLabelWidth = _getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + final double width = widget.width - timeLabelWidth; + _setAppointmentPositionAndMaxPosition( + _appointmentCollection, + widget.calendar, + widget.view, + visibleAppointments, + false, + widget.timeIntervalHeight); + final double cellWidth = width / widget.visibleDates.length; + final double cellHeight = widget.timeIntervalHeight; + double xPosition = timeLabelWidth; + double yPosition = 0; + final double cellEndPadding = widget.calendar.cellEndPadding; + + final int timeInterval = + _getTimeInterval(widget.calendar.timeSlotViewSettings); + for (int i = 0; i < _appointmentCollection.length; i++) { + final _AppointmentView appointmentView = _appointmentCollection[i]; + if (appointmentView.canReuse || appointmentView.appointment == null) { + continue; + } + + final Appointment appointment = appointmentView.appointment; + int column = -1; + final int count = widget.visibleDates.length; + + int datesCount = 0; + for (int j = 0; j < count; j++) { + final DateTime _date = widget.visibleDates[j]; + if (_date != null && + _date.day == appointment._actualStartTime.day && + _date.month == appointment._actualStartTime.month && + _date.year == appointment._actualStartTime.year) { + column = widget.isRTL + ? widget.visibleDates.length - 1 - datesCount + : datesCount; + break; + } else if (_date != null) { + datesCount++; + } + } + + if (column == -1 || + appointment._isSpanned || + (appointment.endTime.difference(appointment.startTime).inDays > 0) || + appointment.isAllDay) { + continue; + } + + final int totalHours = appointment._actualStartTime.hour - + widget.calendar.timeSlotViewSettings.startHour.toInt(); + final double mins = appointment._actualStartTime.minute - + ((widget.calendar.timeSlotViewSettings.startHour - + widget.calendar.timeSlotViewSettings.startHour.toInt()) * + 60); + final int totalMins = (totalHours * 60 + mins).toInt(); + final int row = totalMins ~/ timeInterval; + + final double appointmentWidth = + (cellWidth - cellEndPadding) / appointmentView.maxPositions; + if (widget.isRTL) { + xPosition = column * cellWidth + + (appointmentView.position * appointmentWidth) + + cellEndPadding; + } else { + xPosition = column * cellWidth + + (appointmentView.position * appointmentWidth) + + timeLabelWidth; + } + + yPosition = row * cellHeight; + + Duration difference = + appointment._actualEndTime.difference(appointment._actualStartTime); + final double minuteHeight = cellHeight / timeInterval; + yPosition += ((appointment._actualStartTime.hour * 60 + + appointment._actualStartTime.minute) % + timeInterval) * + minuteHeight; + + double height = difference.inMinutes * minuteHeight; + if (widget.calendar.timeSlotViewSettings.minimumAppointmentDuration != + null && + widget.calendar.timeSlotViewSettings.minimumAppointmentDuration + .inMinutes > + 0) { + if (difference < + widget + .calendar.timeSlotViewSettings.minimumAppointmentDuration && + difference.inMinutes * minuteHeight < + widget.calendar.timeSlotViewSettings.timeIntervalHeight) { + difference = + widget.calendar.timeSlotViewSettings.minimumAppointmentDuration; + height = difference.inMinutes * minuteHeight; + //// Check the minimum appointment duration height does not greater than time interval height. + if (height > + widget.calendar.timeSlotViewSettings.timeIntervalHeight) { + height = widget.calendar.timeSlotViewSettings.timeIntervalHeight; + } + } + } + + final Radius cornerRadius = + Radius.circular((height * 0.1) > 2 ? 2 : (height * 0.1)); + final RRect rect = RRect.fromRectAndRadius( + Rect.fromLTWH(xPosition, yPosition, appointmentWidth - 1, height - 1), + cornerRadius); + appointmentView.appointmentRect = rect; + } + } + + void _updateTimelineAppointmentDetails( + List visibleAppointments) { + final bool isResourceEnabled = + _isResourceEnabled(widget.calendar.dataSource, widget.view); + + /// Filters the appointment for each resource from the visible appointment + /// collection, and assign appointment views for all the collections. + if (isResourceEnabled && visibleAppointments != null) { + for (int i = 0; i < widget.calendar.dataSource.resources.length; i++) { + final CalendarResource resource = + widget.calendar.dataSource.resources[i]; + + /// Filters the appointment for each resource from the visible + /// appointment collection. + final List appointmentForEachResource = visibleAppointments + .where((app) => + app.resourceIds != null && + app.resourceIds.isNotEmpty && + app.resourceIds.contains(resource.id)) + .toList(); + _setAppointmentPositionAndMaxPosition( + _appointmentCollection, + widget.calendar, + widget.view, + appointmentForEachResource, + false, + widget.timeIntervalHeight, + i); + } + } else { + _setAppointmentPositionAndMaxPosition( + _appointmentCollection, + widget.calendar, + widget.view, + visibleAppointments, + false, + widget.timeIntervalHeight); + } + + final double viewWidth = widget.width / widget.visibleDates.length; + final double cellWidth = widget.timeIntervalHeight; + double xPosition = 0; + double yPosition = 0; + final int count = widget.visibleDates.length; + final int timeSlotCount = _getHorizontalLinesCount( + widget.calendar.timeSlotViewSettings, widget.view) + .toInt(); + final int timeInterval = + _getTimeInterval(widget.calendar.timeSlotViewSettings); + final double cellEndPadding = widget.calendar.cellEndPadding; + for (int i = 0; i < _appointmentCollection.length; i++) { + final _AppointmentView appointmentView = _appointmentCollection[i]; + if (appointmentView.canReuse || appointmentView.appointment == null) { + continue; + } + + final Appointment appointment = appointmentView.appointment; + int column = -1; + + DateTime startTime = appointment._actualStartTime; + int datesCount = 0; + for (int j = 0; j < count; j++) { + final DateTime date = widget.visibleDates[j]; + if (date != null && + date.day == startTime.day && + date.month == startTime.month && + date.year == startTime.year) { + column = widget.isRTL + ? widget.visibleDates.length - 1 - datesCount + : datesCount; + break; + } else if (startTime.isBefore(date)) { + column = widget.isRTL + ? widget.visibleDates.length - 1 - datesCount + : datesCount; + startTime = DateTime(date.year, date.month, date.day, 0, 0, 0); + break; + } else if (date != null) { + datesCount++; + } + } + + if (column == -1 && + appointment._actualStartTime.isBefore(widget.visibleDates[0])) { + column = 0; + } + + /// For timeline day, week and work week view each column represents a + /// time slots for timeline month each column represent a day, and as + /// rendering wise the column here represents the day hence the `-1` + /// added in the above calculation not required for timeline month view, + /// hence to rectify this we have added +1. + if (widget.isRTL && widget.view == CalendarView.timelineMonth) { + column += 1; + } + + DateTime endTime = appointment._actualEndTime; + int endColumn = 0; + if (widget.view == CalendarView.timelineWorkWeek) { + endColumn = -1; + datesCount = 0; + for (int j = 0; j < count; j++) { + DateTime date = widget.visibleDates[j]; + if (date != null && + date.day == endTime.day && + date.month == endTime.month && + date.year == endTime.year) { + endColumn = widget.isRTL + ? widget.visibleDates.length - 1 - datesCount + : datesCount; + break; + } else if (endTime.isBefore(date)) { + endColumn = widget.isRTL + ? widget.visibleDates.length - 1 - datesCount - 1 + : datesCount - 1; + if (endColumn != -1) { + date = widget.visibleDates[endColumn]; + endTime = DateTime(date.year, date.month, date.day, 59, 59, 0); + } + break; + } else if (date != null) { + datesCount++; + } + } + + if (endColumn == -1 && + appointment._actualEndTime + .isAfter(widget.visibleDates[widget.visibleDates.length - 1])) { + endColumn = widget.isRTL ? 0 : widget.visibleDates.length - 1; + } + } + + if (column == -1 || endColumn == -1) { + continue; + } + + int row = 0; + int totalHours = 0; + int totalMinutes = 0; + double minutes = 0; + if (widget.view != CalendarView.timelineMonth) { + totalHours = startTime.hour - + widget.calendar.timeSlotViewSettings.startHour.toInt(); + minutes = startTime.minute - + ((widget.calendar.timeSlotViewSettings.startHour - + widget.calendar.timeSlotViewSettings.startHour.toInt()) * + 60); + totalMinutes = (totalHours * 60 + minutes).toInt(); + row = totalMinutes ~/ timeInterval; + if (widget.isRTL) { + row = timeSlotCount - row; + } + } + + final double minuteHeight = cellWidth / timeInterval; + + double appointmentHeight = _getTimelineAppointmentHeight( + widget.calendar.timeSlotViewSettings, widget.view); + final double slotHeight = + isResourceEnabled ? widget.resourceItemHeight : widget.height; + if (appointmentHeight * appointmentView.maxPositions > slotHeight) { + appointmentHeight = slotHeight / appointmentView.maxPositions; + } + + xPosition = (column * viewWidth) + (row * cellWidth); + yPosition = appointmentHeight * appointmentView.position; + if (isResourceEnabled && + appointment.resourceIds != null && + appointment.resourceIds.isNotEmpty) { + /// To render the appointment on specific resource slot, we have got the + /// appointment's resource index and calculated y position based on + /// this. + yPosition += appointmentView.resourceIndex * widget.resourceItemHeight; + } + if (widget.view != CalendarView.timelineMonth) { + /// Calculate the in between minute height + /// Eg., If start time as 12.07 PM and time interval as 60 minutes + /// then the height holds the value of 07 minutes height. + final double inBetweenMinuteHeight = + ((startTime.hour * 60 + startTime.minute) % timeInterval) * + minuteHeight; + if (widget.isRTL) { + /// If the view direction as RTL then we subtract the in between + /// minute height because the value used to calculate the start + /// position of the appointment. + xPosition -= inBetweenMinuteHeight; + } else { + xPosition += inBetweenMinuteHeight; + } + } + + double width = 0; + if (widget.view == CalendarView.timelineWorkWeek) { + totalHours = endTime.hour - + widget.calendar.timeSlotViewSettings.startHour.toInt(); + minutes = endTime.minute - + ((widget.calendar.timeSlotViewSettings.startHour - + widget.calendar.timeSlotViewSettings.startHour.toInt()) * + 60); + totalMinutes = (totalHours * 60 + minutes).toInt(); + row = totalMinutes ~/ timeInterval; + + /// Calculate the in between minute height + /// Eg., If end time as 12.07 PM and time interval as 60 minutes + /// then the height holds the value of 07 minutes height. + double inBetweenMinuteHeight = + ((endTime.hour * 60 + endTime.minute) % timeInterval) * + minuteHeight; + if (widget.isRTL) { + row = timeSlotCount - row; + + /// If the view direction as RTL then we subtract the in between + /// minute height because the value used to calculate the end + /// position of the appointment. + inBetweenMinuteHeight = -inBetweenMinuteHeight; + } + final double endXPosition = + (endColumn * viewWidth) + (row * cellWidth) + inBetweenMinuteHeight; + if (widget.isRTL) { + width = xPosition - endXPosition; + } else { + width = endXPosition - xPosition; + } + } else { + final Duration difference = endTime.difference(startTime); + if (widget.view != CalendarView.timelineMonth) { + /// The width for the appointment UI, calculated based on the minutes + /// difference between the start and end time of the appointment. + width = difference.inMinutes * minuteHeight; + } else { + /// The width for the appointment UI, calculated based on the date + /// difference between the start and end time of the appointment. + width = (difference.inDays + 1) * cellWidth; + + /// For span appointment less than 23 hours the difference will fall + /// as 0 hence to render the appointment on the next day, added one + /// the width for next day. + if (difference.inDays == 0 && endTime.day != startTime.day) { + width += cellWidth; + } + } + } + + if (widget.calendar.timeSlotViewSettings.minimumAppointmentDuration != + null && + widget.calendar.timeSlotViewSettings.minimumAppointmentDuration + .inMinutes > + 0 && + widget.view != CalendarView.timelineMonth) { + final double minWidth = _getAppointmentHeightFromDuration( + widget.calendar.timeSlotViewSettings.minimumAppointmentDuration, + widget.calendar, + widget.timeIntervalHeight); + width = width > minWidth ? width : minWidth; + } + + width = width - cellEndPadding; + final Radius cornerRadius = Radius.circular( + (appointmentHeight * 0.1) > 2 ? 2 : (appointmentHeight * 0.1)); + final RRect rect = RRect.fromRectAndRadius( + Rect.fromLTWH(widget.isRTL ? xPosition - width : xPosition, yPosition, + width, appointmentHeight - 1), + cornerRadius); + appointmentView.appointmentRect = rect; + } + } +} + +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.appointmentCollection, + this.indexAppointments, + this.monthAppointmentCountViews, + {List widgets}) + : super(children: widgets); + + final SfCalendar calendar; + final CalendarView view; + final List visibleDates; + final double timeIntervalHeight; + final bool isRTL; + final SfCalendarThemeData calendarTheme; + final ValueNotifier appointmentHoverPosition; + final List resourceCollection; + final double resourceItemHeight; + final double textScaleFactor; + final bool isMobilePlatform; + final double width; + final double height; + final List visibleAppointments; + final List<_AppointmentView> appointmentCollection; + final Map> indexAppointments; + final Map monthAppointmentCountViews; + + @override + _AppointmentRenderObject createRenderObject(BuildContext context) { + return _AppointmentRenderObject( + calendar, + view, + visibleDates, + visibleAppointments, + timeIntervalHeight, + calendarTheme, + isRTL, + appointmentHoverPosition, + resourceCollection, + resourceItemHeight, + textScaleFactor, + isMobilePlatform, + width, + height, + appointmentCollection, + indexAppointments, + monthAppointmentCountViews); + } + + @override + void updateRenderObject( + BuildContext context, _AppointmentRenderObject renderObject) { + renderObject + ..calendar = calendar + ..view = view + ..visibleDates = visibleDates + ..visibleAppointments = visibleAppointments + ..timeIntervalHeight = timeIntervalHeight + ..calendarTheme = calendarTheme + ..isRTL = isRTL + ..appointmentHoverPosition = appointmentHoverPosition + ..resourceCollection = resourceCollection + ..resourceItemHeight = resourceItemHeight + ..textScaleFactor = textScaleFactor + ..isMobilePlatform = isMobilePlatform + ..width = width + ..height = height + ..appointmentCollection = appointmentCollection + ..indexAppointments = indexAppointments + ..monthAppointmentCountViews = monthAppointmentCountViews; + } +} + +abstract class _CustomCalendarRenderObject extends RenderBox + with ContainerRenderObjectMixin { + @override + void setupParentData(RenderObject child) { + if (child.parentData is! _CalendarParentData) { + child.parentData = _CalendarParentData(); + } + } + + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + return; + } + + @protected + SemanticsBuilderCallback get semanticsBuilder; +} + +class _AppointmentRenderObject extends _CustomCalendarRenderObject { + _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.appointmentCollection, + this.indexAppointments, + this.monthAppointmentCountViews); + + List _visibleAppointments; + + List get visibleAppointments => _visibleAppointments; + + set visibleAppointments(List value) { + if (_isCollectionEqual(_visibleAppointments, value)) { + return; + } + + _visibleAppointments = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + ValueNotifier _appointmentHoverPosition; + + ValueNotifier get appointmentHoverPosition => + _appointmentHoverPosition; + + set appointmentHoverPosition(ValueNotifier value) { + if (_appointmentHoverPosition == value) { + return; + } + + _appointmentHoverPosition?.removeListener(markNeedsPaint); + _appointmentHoverPosition = value; + _appointmentHoverPosition?.addListener(markNeedsPaint); + } + + double _timeIntervalHeight; + + double get timeIntervalHeight => _timeIntervalHeight; + + set timeIntervalHeight(double value) { + if (_timeIntervalHeight == value) { + return; + } + + _timeIntervalHeight = value; + markNeedsLayout(); + } + + double _width; + + double get width => _width; + + set width(double value) { + if (_width == value) { + return; + } + + _width = value; + markNeedsLayout(); + } + + double _height; + + double get height => _height; + + set height(double value) { + if (_height == value) { + return; + } + + _height = value; + markNeedsLayout(); + } + + double _textScaleFactor; + + double get textScaleFactor => _textScaleFactor; + + set textScaleFactor(double value) { + if (_textScaleFactor == value) { + return; + } + + _textScaleFactor = value; + markNeedsPaint(); + } + + SfCalendarThemeData _calendarTheme; + + SfCalendarThemeData get calendarTheme => _calendarTheme; + + set calendarTheme(SfCalendarThemeData value) { + if (_calendarTheme == value) { + return; + } + + _calendarTheme = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + List _visibleDates; + + List get visibleDates => _visibleDates; + + set visibleDates(List value) { + if (_visibleDates == value) { + return; + } + + _visibleDates = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + bool _isRTL; + + bool get isRTL => _isRTL; + + set isRTL(bool value) { + if (_isRTL == value) { + return; + } + + _isRTL = value; + markNeedsPaint(); + } + + double _resourceItemHeight; + + double get resourceItemHeight => _resourceItemHeight; + + set resourceItemHeight(double value) { + if (_resourceItemHeight == value) { + return; + } + + _resourceItemHeight = value; + markNeedsLayout(); + } + + List _resourceCollection; + + List get resourceCollection => _resourceCollection; + + set resourceCollection(List value) { + if (_resourceCollection == value) { + return; + } + + _resourceCollection = value; + markNeedsLayout(); + } + + CalendarView _view; + + CalendarView get view => _view; + + set view(CalendarView value) { + if (_view == value) { + return; + } + + _view = value; + markNeedsLayout(); + } + + SfCalendar _calendar; + + SfCalendar get calendar => _calendar; + + set calendar(SfCalendar value) { + if (_calendar == value) { + return; + } + + _calendar = value; + markNeedsLayout(); + } + + /// attach will called when the render object rendered in view. + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _appointmentHoverPosition?.addListener(markNeedsPaint); + } + + /// detach will called when the render object removed from view. + @override + void detach() { + _appointmentHoverPosition?.removeListener(markNeedsPaint); + super.detach(); + } + + bool isMobilePlatform; + List<_AppointmentView> appointmentCollection; + Map> indexAppointments; + Map monthAppointmentCountViews; + + Paint _appointmentPainter; + TextPainter _textPainter; + + @override + bool get isRepaintBoundary => true; + + @override + List Function(Size size) get semanticsBuilder => + _getSemanticsBuilder; + + List _getSemanticsBuilder(Size size) { + final List semanticsBuilder = + []; + if (appointmentCollection == null || appointmentCollection.isEmpty) { + return semanticsBuilder; + } + + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView appointmentView = appointmentCollection[i]; + if (appointmentView.appointment == null || + appointmentView.appointmentRect == null) { + continue; + } + + semanticsBuilder.add(CustomPainterSemantics( + rect: appointmentView.appointmentRect?.outerRect, + properties: SemanticsProperties( + label: _getAppointmentText(appointmentView.appointment), + textDirection: TextDirection.ltr, + ), + )); + } + + if (view != CalendarView.month || + calendar.monthViewSettings.appointmentDisplayMode != + MonthAppointmentDisplayMode.appointment) { + return semanticsBuilder; + } + + final List keys = monthAppointmentCountViews.keys.toList(); + for (int i = 0; i < keys.length; i++) { + final RRect moreRegionRect = monthAppointmentCountViews[keys[i]]; + semanticsBuilder.add(CustomPainterSemantics( + rect: moreRegionRect.outerRect, + properties: SemanticsProperties( + label: 'More', + textDirection: TextDirection.ltr, + ), + )); + } + + return semanticsBuilder; + } + + @override + void performLayout() { + final Size widgetSize = constraints.biggest; + size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height); + RenderBox child = firstChild; + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView appointmentView = appointmentCollection[i]; + if (appointmentView.appointment == null || + child == null || + appointmentView.appointmentRect == null) { + continue; + } + + child.layout(constraints.copyWith( + minHeight: appointmentView.appointmentRect.height, + maxHeight: appointmentView.appointmentRect.height, + minWidth: appointmentView.appointmentRect.width, + maxWidth: appointmentView.appointmentRect.width)); + child = childAfter(child); + } + + if (view != CalendarView.month || + calendar.monthViewSettings.appointmentDisplayMode != + MonthAppointmentDisplayMode.appointment) { + return; + } + + final List keys = monthAppointmentCountViews.keys.toList(); + for (int i = 0; i < keys.length; i++) { + if (child == null) { + continue; + } - List visibleAppointments; - List<_AppointmentView> _appointmentCollection; - Map> _indexAppointments; - List _monthAppointmentCountViews; - Paint _appointmentPainter; - TextPainter _textPainter; - final _UpdateCalendarStateDetails _updateCalendarStateDetails = - _UpdateCalendarStateDetails(); + final RRect moreRegionRect = monthAppointmentCountViews[keys[i]]; + child.layout(constraints.copyWith( + minHeight: moreRegionRect.height, + maxHeight: moreRegionRect.height, + minWidth: moreRegionRect.width, + maxWidth: moreRegionRect.width)); + child = childAfter(child); + } + } @override - void paint(Canvas canvas, Size size) { - updateCalendarState(_updateCalendarStateDetails); - visibleAppointments = _updateCalendarStateDetails._visibleAppointments; - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - _appointmentPainter = _appointmentPainter ?? Paint(); - _appointmentPainter.isAntiAlias = true; - _appointmentCollection = _appointmentCollection ?? <_AppointmentView>[]; + void paint(PaintingContext context, Offset offset) { + RenderBox child = firstChild; + final bool isNeedDefaultPaint = childCount == 0; + if (isNeedDefaultPaint) { + _textPainter = _textPainter ?? TextPainter(); + _appointmentPainter = _appointmentPainter ?? Paint(); + _drawCustomAppointmentView(context.canvas); + } else { + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView appointmentView = appointmentCollection[i]; + if (appointmentView.appointment == null || + child == null || + appointmentView.appointmentRect == null) { + continue; + } - _resetAppointmentView(_appointmentCollection); + child.paint( + context, + Offset(appointmentView.appointmentRect.left, + appointmentView.appointmentRect.top)); + if (appointmentHoverPosition != null) { + _appointmentPainter ??= Paint(); + _updateAppointmentHovering( + appointmentView.appointmentRect, context.canvas); + } - if (visibleDates != _updateCalendarStateDetails._currentViewVisibleDates) { - return; + child = childAfter(child); + } + + if (view != CalendarView.month || + calendar.monthViewSettings.appointmentDisplayMode != + MonthAppointmentDisplayMode.appointment) { + return; + } + + final List keys = monthAppointmentCountViews.keys.toList(); + for (int i = 0; i < keys.length; i++) { + if (child == null) { + continue; + } + + final RRect moreRegionRect = monthAppointmentCountViews[keys[i]]; + child.paint(context, Offset(moreRegionRect.left, moreRegionRect.top)); + if (appointmentHoverPosition != null) { + _appointmentPainter ??= Paint(); + _updateAppointmentHovering(moreRegionRect, context.canvas); + } + + child = childAfter(child); + } } + } + void _drawCustomAppointmentView(Canvas canvas) { + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + _appointmentPainter = _appointmentPainter ?? Paint(); + _appointmentPainter.isAntiAlias = true; switch (view) { case CalendarView.month: { @@ -92,39 +1363,10 @@ class _AppointmentPainter extends CustomPainter { } } - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _AppointmentPainter oldWidget = oldDelegate; - if (oldWidget.visibleDates != visibleDates || - oldWidget.visibleAppointments != visibleAppointments || - oldWidget.view != view || - oldWidget.isRTL != isRTL || - oldWidget.resourceCollection != resourceCollection || - oldWidget.appointmentHoverPosition != appointmentHoverPosition || - oldWidget.textScaleFactor != textScaleFactor) { - return true; - } - - _appointmentCollection = oldWidget._appointmentCollection; - return false; - } - - void _updateTextPainter(TextSpan span) { - _textPainter = _textPainter ?? TextPainter(); - _textPainter.text = span; - _textPainter.maxLines = 1; - _textPainter.textDirection = TextDirection.ltr; - _textPainter.textAlign = isRTL ? TextAlign.right : TextAlign.left; - _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; - } - void _drawMonthAppointment(Canvas canvas, Size size, Paint paint) { final double cellWidth = size.width / _kNumberOfDaysInWeek; final double cellHeight = size.height / calendar.monthViewSettings.numberOfWeeksInView; - _monthAppointmentCountViews = []; - _indexAppointments = >{}; if (calendar.monthCellBuilder != null) { return; } @@ -147,10 +1389,6 @@ class _AppointmentPainter extends CustomPainter { final int count = visibleDates.length; DateTime visibleStartDate = _convertToStartTime(visibleDates[0]); DateTime visibleEndDate = _convertToEndTime(visibleDates[count - 1]); - int visibleStartIndex = 0; - int visibleEndIndex = (calendar.monthViewSettings.numberOfWeeksInView * - _kNumberOfDaysInWeek) - - 1; final bool showTrailingLeadingDates = _isLeadingAndTrailingDatesVisible( calendar.monthViewSettings.numberOfWeeksInView, calendar.monthViewSettings.showTrailingAndLeadingDates); @@ -159,48 +1397,26 @@ class _AppointmentPainter extends CustomPainter { visibleStartDate = _convertToStartTime(_getMonthStartDate(currentMonthDate)); visibleEndDate = _convertToEndTime(_getMonthEndDate(currentMonthDate)); - visibleStartIndex = _getIndex(visibleDates, visibleStartDate); - visibleEndIndex = _getIndex(visibleDates, visibleEndDate); } - _updateAppointment(this, visibleStartIndex, visibleEndIndex); - final TextStyle style = - calendar.todayTextStyle ?? calendarTheme.todayTextStyle; - final TextSpan dateText = - TextSpan(text: DateTime.now().day.toString(), style: style); - _updateTextPainter(dateText); - - /// cell padding and start position calculated by month date cell - /// rendering padding and size. - /// Cell padding value includes month cell text top padding(5) and circle - /// top(4) and bottom(4) padding - const double cellPadding = 13; - - /// Today circle radius as circle radius added after the text height. - const double todayCircleRadius = 5; - final double startPosition = - cellPadding + _textPainter.preferredLineHeight + todayCircleRadius; final int maximumDisplayCount = calendar.monthViewSettings.appointmentDisplayCount ?? 3; - final double appointmentHeight = - (cellHeight - startPosition) / maximumDisplayCount; double textSize = -1; // right side padding used to add padding on appointment view right side // in month view - const int rightSidePadding = 4; - for (int i = 0; i < _appointmentCollection.length; i++) { - final _AppointmentView appointmentView = _appointmentCollection[i]; - if (appointmentView.canReuse || appointmentView.appointment == null) { + final bool useMobilePlatformUI = + _isMobileLayoutUI(size.width, isMobilePlatform); + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView appointmentView = appointmentCollection[i]; + if (appointmentView.canReuse || + appointmentView.appointment == null || + appointmentView.appointmentRect == null) { continue; } if (appointmentView.position < maximumDisplayCount || (appointmentView.position == maximumDisplayCount && appointmentView.maxPositions == maximumDisplayCount)) { - final double appointmentWidth = - (appointmentView.endIndex - appointmentView.startIndex + 1) * - cellWidth; - final Appointment appointment = appointmentView.appointment; final bool canAddSpanIcon = _canAddSpanIcon( visibleDates, appointment, view, @@ -208,55 +1424,23 @@ class _AppointmentPainter extends CustomPainter { visibleEndDate: visibleEndDate, showTrailingLeadingDates: showTrailingLeadingDates); - if (isRTL) { - xPosition = - (6 - (appointmentView.startIndex % _kNumberOfDaysInWeek)) * - cellWidth; - xPosition -= appointmentWidth - cellWidth; - } else { - xPosition = - (appointmentView.startIndex % _kNumberOfDaysInWeek) * cellWidth; - } - - yPosition = - (appointmentView.startIndex ~/ _kNumberOfDaysInWeek) * cellHeight; - if (appointmentView.position <= maximumDisplayCount) { - yPosition = yPosition + - startPosition + - (appointmentHeight * (appointmentView.position - 1)); - } else { - yPosition = yPosition + - startPosition + - (appointmentHeight * (maximumDisplayCount - 1)); - } - - final Radius cornerRadius = Radius.circular( - (appointmentHeight * 0.1) > 2 ? 2 : (appointmentHeight * 0.1)); - final RRect rect = RRect.fromRectAndRadius( - Rect.fromLTWH( - isRTL ? xPosition + rightSidePadding : xPosition, - yPosition, - appointmentWidth - rightSidePadding > 0 - ? appointmentWidth - rightSidePadding - : 0, - appointmentHeight - 1), - cornerRadius); - paint.color = appointment.color; TextStyle style = calendar.appointmentTextStyle; TextSpan span = TextSpan(text: appointment.subject, style: style); - _updateTextPainter(span); + _textPainter = + _updateTextPainter(span, _textPainter, isRTL, _textScaleFactor); if (textSize == -1) { //// left and right side padding value 2 subtracted in appointment width - double maxTextWidth = appointmentWidth - 2; + double maxTextWidth = appointmentView.appointmentRect.width - 2; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; for (double j = style.fontSize - 1; j > 0; j--) { _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); - if (_textPainter.height >= appointmentHeight - 1) { + if (_textPainter.height >= appointmentView.appointmentRect.height) { style = style.copyWith(fontSize: j); span = TextSpan(text: appointment.subject, style: style); - _updateTextPainter(span); + _textPainter = _updateTextPainter( + span, _textPainter, isRTL, _textScaleFactor); } else { textSize = j + 1; break; @@ -266,11 +1450,11 @@ class _AppointmentPainter extends CustomPainter { span = TextSpan( text: appointment.subject, style: style.copyWith(fontSize: textSize)); - _updateTextPainter(span); + _textPainter = + _updateTextPainter(span, _textPainter, isRTL, _textScaleFactor); } - appointmentView.appointmentRect = rect; - canvas.drawRRect(rect, paint); + canvas.drawRRect(appointmentView.appointmentRect, paint); final bool isRecurrenceAppointment = appointment.recurrenceRule != null && @@ -279,32 +1463,46 @@ class _AppointmentPainter extends CustomPainter { /// 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 = appointmentWidth - - rightSidePadding - + final double textWidth = appointmentView.appointmentRect.width - (isRecurrenceAppointment ? textSize : 1); _textPainter.layout( minWidth: 0, maxWidth: textWidth > 0 ? textWidth : 0); - yPosition += ((appointmentHeight - 1 - _textPainter.height) / 2); + xPosition = appointmentView.appointmentRect.left; + yPosition = appointmentView.appointmentRect.top; + yPosition += ((appointmentView.appointmentRect.height - + 1 - + _textPainter.height) / + 2); if (isRTL && !canAddSpanIcon) { - xPosition += appointmentWidth - _textPainter.width - 2; + xPosition += + appointmentView.appointmentRect.width - _textPainter.width - 2; } if (canAddSpanIcon) { - xPosition += (appointmentWidth - _textPainter.width) / 2; + xPosition += + (appointmentView.appointmentRect.width - _textPainter.width) / 2; } _textPainter.paint(canvas, Offset(xPosition + 2, yPosition)); if (isRecurrenceAppointment) { - _drawRecurrenceIconForMonth(canvas, size, style, textSize, yPosition, - rect, cornerRadius, paint); + _drawRecurrenceIconForMonth( + canvas, + size, + style, + textSize, + yPosition, + appointmentView.appointmentRect, + appointmentView.appointmentRect.tlRadius, + paint, + useMobilePlatformUI); } if (canAddSpanIcon) { final int appStartIndex = - _getDateIndex(appointment._exactStartTime, this); + _getDateIndex(appointment._exactStartTime, visibleDates); final int appEndIndex = - _getDateIndex(appointment._exactEndTime, this); + _getDateIndex(appointment._exactEndTime, visibleDates); if (appStartIndex == appointmentView.startIndex && appEndIndex == appointmentView.endIndex) { continue; @@ -313,73 +1511,71 @@ class _AppointmentPainter extends CustomPainter { if (appStartIndex != appointmentView.startIndex && appEndIndex != appointmentView.endIndex) { _drawForwardSpanIconForMonth( - canvas, size, style, textSize, rect, cornerRadius, paint); + canvas, + size, + style, + textSize, + appointmentView.appointmentRect, + appointmentView.appointmentRect.tlRadius, + paint, + useMobilePlatformUI); _drawBackwardSpanIconForMonth( - canvas, style, textSize, rect, cornerRadius, paint); + canvas, + style, + textSize, + appointmentView.appointmentRect, + appointmentView.appointmentRect.tlRadius, + paint); } else if (appEndIndex != appointmentView.endIndex) { _drawForwardSpanIconForMonth( - canvas, size, style, textSize, rect, cornerRadius, paint); + canvas, + size, + style, + textSize, + appointmentView.appointmentRect, + appointmentView.appointmentRect.tlRadius, + paint, + useMobilePlatformUI); } else { _drawBackwardSpanIconForMonth( - canvas, style, textSize, rect, cornerRadius, paint); + canvas, + style, + textSize, + appointmentView.appointmentRect, + appointmentView.appointmentRect.tlRadius, + paint); } } if (appointmentHoverPosition != null) { paint ??= Paint(); - _updateAppointmentHovering(rect, canvas, paint); + _updateAppointmentHovering(appointmentView.appointmentRect, canvas); } } } const double padding = 2; const double startPadding = 5; - double radius = appointmentHeight * 0.12; - if (radius > 3) { - radius = 3; - } + double radius; - final List keys = _indexAppointments.keys.toList(); + final List keys = monthAppointmentCountViews.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; - if (maxPosition <= maximumDisplayCount) { - continue; - } - if (isRTL) { - xPosition = (6 - (_index % _kNumberOfDaysInWeek)) * cellWidth; - } else { - xPosition = (_index % _kNumberOfDaysInWeek) * cellWidth; - } - - yPosition = ((_index ~/ _kNumberOfDaysInWeek) * cellHeight) + - cellHeight - - appointmentHeight; - double startXPosition = xPosition + startPadding; - if (isRTL) { - startXPosition = xPosition + (cellWidth - startPadding); + final int index = keys[i]; + final RRect moreRegionRect = monthAppointmentCountViews[index]; + if (radius == null) { + radius = moreRegionRect.height * 0.12; + if (radius > 3) { + radius = 3; + } } - - final RRect hoveringRect = RRect.fromRectAndRadius( - Rect.fromLTWH( - isRTL ? xPosition + rightSidePadding : xPosition, - yPosition, - cellWidth - rightSidePadding > 0 - ? cellWidth - rightSidePadding - : 0, - appointmentHeight - 1), - const Radius.circular(0)); - + 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, yPosition + (appointmentHeight / 2)), + Offset(startXPosition, + moreRegionRect.top + (moreRegionRect.height / 2)), radius, paint); if (isRTL) { @@ -389,16 +1585,22 @@ class _AppointmentPainter extends CustomPainter { } } - _monthAppointmentCountViews.add(hoveringRect); if (appointmentHoverPosition != null) { paint ??= Paint(); - _updateAppointmentHovering(hoveringRect, canvas, paint); + _updateAppointmentHovering(moreRegionRect, canvas); } } } - void _drawForwardSpanIconForMonth(Canvas canvas, Size size, TextStyle style, - double textSize, RRect rect, Radius cornerRadius, Paint paint) { + void _drawForwardSpanIconForMonth( + Canvas canvas, + Size size, + TextStyle style, + double textSize, + RRect rect, + Radius cornerRadius, + Paint paint, + bool useMobilePlatformUI) { final TextSpan icon = _getSpanIcon(style.color, textSize, isRTL ? false : true); _textPainter.text = icon; @@ -406,8 +1608,7 @@ class _AppointmentPainter extends CustomPainter { minWidth: 0, maxWidth: rect.width + 1 > 0 ? rect.width + 1 : 0); final double yPosition = _getYPositionForSpanIcon(icon, _textPainter, rect); - final double rightPadding = - kIsWeb && size.width > _kMobileViewWidth ? 2 : 0; + final double rightPadding = useMobilePlatformUI ? 0 : 2; final double xPosition = isRTL ? rect.left + rightPadding : rect.right - _textPainter.width - rightPadding; @@ -441,6 +1642,34 @@ class _AppointmentPainter extends CustomPainter { _textPainter.paint(canvas, Offset(xPosition, yPosition)); } + void _drawRecurrenceIconForMonth( + Canvas canvas, + Size size, + TextStyle style, + double textSize, + double yPosition, + RRect rect, + Radius cornerRadius, + Paint paint, + bool useMobilePlatformUI) { + final TextSpan icon = _getRecurrenceIcon(style.color, textSize); + _textPainter.text = icon; + _textPainter.layout( + minWidth: 0, maxWidth: rect.width + 1 > 0 ? rect.width + 1 : 0); + yPosition = rect.top + ((rect.height - _textPainter.height) / 2); + final double rightPadding = useMobilePlatformUI ? 0 : 2; + 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), + cornerRadius), + paint); + _textPainter.paint(canvas, Offset(recurrenceStartPosition, yPosition)); + } + void _drawMonthAppointmentIndicator( Canvas canvas, double cellWidth, double cellHeight, Paint paint) { double xPosition = 0; @@ -499,148 +1728,51 @@ class _AppointmentPainter extends CustomPainter { canvas.drawCircle( Offset(xPosition, yPosition - bottomPadding), radius, paint); xPosition += diameter + indicatorPadding; - if (startXPosition + cellWidth < xPosition + diameter) { - break; - } - } - } - } - - void _drawRecurrenceIconForMonth( - Canvas canvas, - Size size, - TextStyle style, - double textSize, - double yPosition, - RRect rect, - Radius cornerRadius, - Paint paint) { - final TextSpan icon = _getRecurrenceIcon(style.color, textSize); - _textPainter.text = icon; - _textPainter.layout( - minWidth: 0, maxWidth: rect.width + 1 > 0 ? rect.width + 1 : 0); - yPosition = rect.top + ((rect.height - _textPainter.height) / 2); - final double rightPadding = - kIsWeb && size.width > _kMobileViewWidth ? 2 : 0; - 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), - cornerRadius), - paint); - _textPainter.paint(canvas, Offset(recurrenceStartPosition, yPosition)); - } - - void _drawDayAppointments(Canvas canvas, Size size, Paint paint) { - final double timeLabelWidth = - _getTimeLabelWidth(calendar.timeSlotViewSettings.timeRulerSize, view); - final double width = size.width - timeLabelWidth; - _setAppointmentPositionAndMaxPosition( - this, calendar, view, visibleAppointments, false, timeIntervalHeight); - final double cellWidth = width / visibleDates.length; - final double cellHeight = timeIntervalHeight; - double xPosition = timeLabelWidth; - double yPosition = 0; - const int textStartPadding = 3; - double rightSidePadding = cellWidth * 0.1; - if (view == CalendarView.day) { - rightSidePadding = rightSidePadding > 10 ? 10 : rightSidePadding; - } else { - rightSidePadding = rightSidePadding > 5 ? 5 : rightSidePadding; - } - - final int timeInterval = _getTimeInterval(calendar.timeSlotViewSettings); - for (int i = 0; i < _appointmentCollection.length; i++) { - final _AppointmentView appointmentView = _appointmentCollection[i]; - if (appointmentView.canReuse) { - continue; - } - - final Appointment appointment = appointmentView.appointment; - int column = -1; - final int count = visibleDates.length; - - int datesCount = 0; - for (int j = 0; j < count; j++) { - final DateTime _date = visibleDates[j]; - if (_date != null && - _date.day == appointment._actualStartTime.day && - _date.month == appointment._actualStartTime.month && - _date.year == appointment._actualStartTime.year) { - column = isRTL ? visibleDates.length - 1 - datesCount : datesCount; - break; - } else if (_date != null) { - datesCount++; - } - } - - if (column == -1 || - appointment._isSpanned || - (appointment.endTime.difference(appointment.startTime).inDays > 0) || - appointment.isAllDay) { - continue; - } - - final int totalHours = appointment._actualStartTime.hour - - calendar.timeSlotViewSettings.startHour.toInt(); - final double mins = appointment._actualStartTime.minute - - ((calendar.timeSlotViewSettings.startHour - - calendar.timeSlotViewSettings.startHour.toInt()) * - 60); - final int totalMins = (totalHours * 60 + mins).toInt(); - final int row = totalMins ~/ timeInterval; - - final double appointmentWidth = - (cellWidth - rightSidePadding) / appointmentView.maxPositions; - if (isRTL) { - xPosition = column * cellWidth + - (appointmentView.position * appointmentWidth) + - rightSidePadding; - } else { - xPosition = column * cellWidth + - (appointmentView.position * appointmentWidth) + - timeLabelWidth; - } - - yPosition = row * cellHeight; - - Duration difference = - appointment._actualEndTime.difference(appointment._actualStartTime); - final double minuteHeight = cellHeight / timeInterval; - yPosition += ((appointment._actualStartTime.hour * 60 + - appointment._actualStartTime.minute) % - timeInterval) * - minuteHeight; - - double height = difference.inMinutes * minuteHeight; - if (calendar.timeSlotViewSettings.minimumAppointmentDuration != null && - calendar.timeSlotViewSettings.minimumAppointmentDuration.inMinutes > - 0) { - if (difference < - calendar.timeSlotViewSettings.minimumAppointmentDuration && - difference.inMinutes * minuteHeight < - calendar.timeSlotViewSettings.timeIntervalHeight) { - difference = calendar.timeSlotViewSettings.minimumAppointmentDuration; - height = difference.inMinutes * minuteHeight; - //// Check the minimum appointment duration height does not greater than time interval height. - if (height > calendar.timeSlotViewSettings.timeIntervalHeight) { - height = calendar.timeSlotViewSettings.timeIntervalHeight; - } + if (startXPosition + cellWidth < xPosition + diameter) { + break; } } + } + } - final Radius cornerRadius = - Radius.circular((height * 0.1) > 2 ? 2 : (height * 0.1)); + void _updateAppointmentHovering(RRect rect, Canvas canvas) { + final Offset hoverPosition = appointmentHoverPosition.value; + if (hoverPosition == null) { + return; + } + + if (rect.left < hoverPosition.dx && + rect.right > hoverPosition.dx && + rect.top < hoverPosition.dy && + rect.bottom > hoverPosition.dy) { + _appointmentPainter.color = + calendarTheme.selectionBorderColor.withOpacity(0.4); + _appointmentPainter.strokeWidth = 2; + _appointmentPainter.style = PaintingStyle.stroke; + canvas.drawRect(rect.outerRect, _appointmentPainter); + _appointmentPainter.style = PaintingStyle.fill; + } + } + + void _drawDayAppointments(Canvas canvas, Size size, Paint paint) { + const int textStartPadding = 3; + + final bool useMobilePlatformUI = + _isMobileLayoutUI(size.width, isMobilePlatform); + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView appointmentView = appointmentCollection[i]; + if (appointmentView.canReuse || + appointmentView.appointmentRect == null || + appointmentView.appointment == null) { + continue; + } + + final Appointment appointment = appointmentView.appointment; paint.color = appointment.color; - final RRect rect = RRect.fromRectAndRadius( - Rect.fromLTWH(xPosition, yPosition, appointmentWidth - 1, height - 1), - cornerRadius); - appointmentView.appointmentRect = rect; - canvas.drawRRect(rect, paint); + canvas.drawRRect(appointmentView.appointmentRect, paint); + double xPosition = appointmentView.appointmentRect.left; + double yPosition = appointmentView.appointmentRect.top; final bool canAddSpanIcon = _canAddSpanIcon(visibleDates, appointment, view); bool canAddForwardIcon = false; @@ -654,8 +1786,8 @@ class _AppointmentPainter extends CustomPainter { appointment._exactStartTime, appointment._actualStartTime) && _isSameTimeSlot( appointment._exactEndTime, appointment._actualEndTime)) { - yPosition += _getTextSize( - rect, (calendar.appointmentTextStyle.fontSize * textScaleFactor)); + yPosition += _getTextSize(appointmentView.appointmentRect, + (calendar.appointmentTextStyle.fontSize * textScaleFactor)); } } @@ -664,13 +1796,16 @@ class _AppointmentPainter extends CustomPainter { style: calendar.appointmentTextStyle, ); - _updateTextPainter(span); + _textPainter = + _updateTextPainter(span, _textPainter, isRTL, _textScaleFactor); - final double totalHeight = height - textStartPadding - 1; + final double totalHeight = + appointmentView.appointmentRect.height - textStartPadding; _updatePainterMaxLines(totalHeight); //// left and right side padding value 2 subtracted in appointment width - double maxTextWidth = appointmentWidth - textStartPadding - 1; + double maxTextWidth = + appointmentView.appointmentRect.width - textStartPadding; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); @@ -681,7 +1816,8 @@ class _AppointmentPainter extends CustomPainter { /// return second lines width. /// We are using the minIntrinsicWidth to restrict the text rendering /// when the appointment view bound does not hold single letter. - final double textWidth = appointmentWidth - textStartPadding; + final double textWidth = + appointmentView.appointmentRect.width - textStartPadding; if (textWidth < _textPainter.minIntrinsicWidth && textWidth < _textPainter.width && textWidth < @@ -689,7 +1825,7 @@ class _AppointmentPainter extends CustomPainter { textScaleFactor) { if (appointmentHoverPosition != null) { paint ??= Paint(); - _updateAppointmentHovering(rect, canvas, paint); + _updateAppointmentHovering(appointmentView.appointmentRect, canvas); } continue; @@ -699,35 +1835,46 @@ class _AppointmentPainter extends CustomPainter { _textPainter.height > totalHeight) { if (appointmentHoverPosition != null) { paint ??= Paint(); - _updateAppointmentHovering(rect, canvas, paint); + _updateAppointmentHovering(appointmentView.appointmentRect, canvas); } continue; } if (isRTL) { - xPosition += appointmentWidth - textStartPadding - _textPainter.width; + xPosition += appointmentView.appointmentRect.width - + textStartPadding - + _textPainter.width; } _textPainter.paint(canvas, Offset(xPosition + textStartPadding, yPosition + textStartPadding)); if (appointment.recurrenceRule != null && appointment.recurrenceRule.isNotEmpty) { - _addRecurrenceIconForDay(canvas, size, rect, appointmentWidth, - textStartPadding, paint, cornerRadius); + _addRecurrenceIconForDay( + canvas, + size, + appointmentView.appointmentRect, + appointmentView.appointmentRect.width, + textStartPadding, + paint, + appointmentView.appointmentRect.tlRadius, + useMobilePlatformUI); } if (canAddSpanIcon) { if (canAddForwardIcon) { - _addForwardSpanIconForDay(canvas, rect, size, cornerRadius, paint); + _addForwardSpanIconForDay(canvas, appointmentView.appointmentRect, + size, appointmentView.appointmentRect.tlRadius, paint); } else { - _addBackwardSpanIconForDay(canvas, rect, size, cornerRadius, paint); + _addBackwardSpanIconForDay(canvas, appointmentView.appointmentRect, + size, appointmentView.appointmentRect.tlRadius, paint); } } if (appointmentHoverPosition != null) { paint ??= Paint(); - _updateAppointmentHovering(rect, canvas, paint); + _updateAppointmentHovering(appointmentView.appointmentRect, canvas); } } } @@ -740,7 +1887,8 @@ class _AppointmentPainter extends CustomPainter { _getTextSize(rect, calendar.appointmentTextStyle.fontSize); final TextSpan icon = _getSpanIcon(calendar.appointmentTextStyle.color, textSize, false); - _updateTextPainter(icon); + _textPainter = + _updateTextPainter(icon, _textPainter, isRTL, _textScaleFactor); _textPainter.layout(minWidth: 0, maxWidth: rect.width); canvas.drawRRect( RRect.fromRectAndRadius( @@ -781,7 +1929,8 @@ class _AppointmentPainter extends CustomPainter { _getTextSize(rect, calendar.appointmentTextStyle.fontSize); final TextSpan icon = _getSpanIcon(calendar.appointmentTextStyle.color, textSize, true); - _updateTextPainter(icon); + _textPainter = + _updateTextPainter(icon, _textPainter, isRTL, _textScaleFactor); _textPainter.layout(minWidth: 0, maxWidth: rect.width); canvas.drawRRect( RRect.fromRectAndRadius( @@ -819,8 +1968,9 @@ class _AppointmentPainter extends CustomPainter { double appointmentWidth, int textPadding, Paint paint, - Radius cornerRadius) { - final double xPadding = kIsWeb && size.width > _kMobileViewWidth ? 2 : 1; + Radius cornerRadius, + bool useMobilePlatformUI) { + final double xPadding = useMobilePlatformUI ? 1 : 2; const double bottomPadding = 2; double textSize = calendar.appointmentTextStyle.fontSize; if (rect.width < textSize || rect.height < textSize) { @@ -851,254 +2001,30 @@ class _AppointmentPainter extends CustomPainter { } void _drawTimelineAppointments(Canvas canvas, Size size, Paint paint) { - final bool isResourceEnabled = - _isResourceEnabled(calendar.dataSource, view); - - /// Filters the appointment for each resource from the visible appointment - /// collection, and assign appointment views for all the collections. - if (isResourceEnabled && visibleAppointments != null) { - for (int i = 0; i < calendar.dataSource.resources.length; i++) { - final CalendarResource resource = calendar.dataSource.resources[i]; - - /// Filters the appointment for each resource from the visible - /// appointment collection. - final List appointmentForEachResource = visibleAppointments - .where((app) => - app.resourceIds != null && - app.resourceIds.isNotEmpty && - app.resourceIds.contains(resource.id)) - .toList(); - _setAppointmentPositionAndMaxPosition(this, calendar, view, - appointmentForEachResource, false, timeIntervalHeight, i); - } - } else { - _setAppointmentPositionAndMaxPosition( - this, calendar, view, visibleAppointments, false, timeIntervalHeight); - } - - final double viewWidth = size.width / visibleDates.length; - final double cellWidth = timeIntervalHeight; - double xPosition = 0; - double yPosition = 0; const int textStartPadding = 3; - final int count = visibleDates.length; - final int timeSlotCount = - _getHorizontalLinesCount(calendar.timeSlotViewSettings, view).toInt(); - final int timeInterval = _getTimeInterval(calendar.timeSlotViewSettings); - for (int i = 0; i < _appointmentCollection.length; i++) { - final _AppointmentView appointmentView = _appointmentCollection[i]; - if (appointmentView.canReuse) { + final bool useMobilePlatformUI = + _isMobileLayoutUI(size.width, isMobilePlatform); + for (int i = 0; i < appointmentCollection.length; i++) { + final _AppointmentView appointmentView = appointmentCollection[i]; + if (appointmentView.canReuse || + appointmentView.appointmentRect == null || + appointmentView.appointment == null) { continue; } final Appointment appointment = appointmentView.appointment; - int column = -1; - - DateTime startTime = appointment._actualStartTime; - int datesCount = 0; - for (int j = 0; j < count; j++) { - final DateTime date = visibleDates[j]; - if (date != null && - date.day == startTime.day && - date.month == startTime.month && - date.year == startTime.year) { - column = isRTL ? visibleDates.length - 1 - datesCount : datesCount; - break; - } else if (startTime.isBefore(date)) { - column = isRTL ? visibleDates.length - 1 - datesCount : datesCount; - startTime = DateTime(date.year, date.month, date.day, 0, 0, 0); - break; - } else if (date != null) { - datesCount++; - } - } - - if (column == -1 && - appointment._actualStartTime.isBefore(visibleDates[0])) { - column = 0; - } - - /// For timeline day, week and work week view each column represents a - /// time slots for timeline month each column represent a day, and as - /// rendering wise the column here represents the day hence the `-1` - /// added in the above calculation not required for timeline month view, - /// hence to rectify this we have added +1. - if (isRTL && view == CalendarView.timelineMonth) { - column += 1; - } - - DateTime endTime = appointment._actualEndTime; - int endColumn = 0; - if (view == CalendarView.timelineWorkWeek) { - endColumn = -1; - datesCount = 0; - for (int j = 0; j < count; j++) { - DateTime date = visibleDates[j]; - if (date != null && - date.day == endTime.day && - date.month == endTime.month && - date.year == endTime.year) { - endColumn = - isRTL ? visibleDates.length - 1 - datesCount : datesCount; - break; - } else if (endTime.isBefore(date)) { - endColumn = isRTL - ? visibleDates.length - 1 - datesCount - 1 - : datesCount - 1; - if (endColumn != -1) { - date = visibleDates[endColumn]; - endTime = DateTime(date.year, date.month, date.day, 59, 59, 0); - } - break; - } else if (date != null) { - datesCount++; - } - } - - if (endColumn == -1 && - appointment._actualEndTime - .isAfter(visibleDates[visibleDates.length - 1])) { - endColumn = isRTL ? 0 : visibleDates.length - 1; - } - } - - if (column == -1 || endColumn == -1) { - continue; - } - - int row = 0; - int totalHours = 0; - int totalMinutes = 0; - double minutes = 0; - if (view != CalendarView.timelineMonth) { - totalHours = - startTime.hour - calendar.timeSlotViewSettings.startHour.toInt(); - minutes = startTime.minute - - ((calendar.timeSlotViewSettings.startHour - - calendar.timeSlotViewSettings.startHour.toInt()) * - 60); - totalMinutes = (totalHours * 60 + minutes).toInt(); - row = totalMinutes ~/ timeInterval; - if (isRTL) { - row = timeSlotCount - row; - } - } - - final double minuteHeight = cellWidth / timeInterval; - - double appointmentHeight = - _getTimelineAppointmentHeight(calendar.timeSlotViewSettings, view); - final double slotHeight = - isResourceEnabled ? resourceItemHeight : size.height; - if (appointmentHeight * appointmentView.maxPositions > slotHeight) { - appointmentHeight = slotHeight / appointmentView.maxPositions; - } - - xPosition = (column * viewWidth) + (row * cellWidth); - yPosition = appointmentHeight * appointmentView.position; - if (isResourceEnabled && - appointment.resourceIds != null && - appointment.resourceIds.isNotEmpty) { - /// To render the appointment on specific resource slot, we have got the - /// appointment's resource index and calculated y position based on - /// this. - yPosition += appointmentView.resourceIndex * resourceItemHeight; - } - if (view != CalendarView.timelineMonth) { - /// Calculate the in between minute height - /// Eg., If start time as 12.07 PM and time interval as 60 minutes - /// then the height holds the value of 07 minutes height. - final double inBetweenMinuteHeight = - ((startTime.hour * 60 + startTime.minute) % timeInterval) * - minuteHeight; - if (isRTL) { - /// If the view direction as RTL then we subtract the in between - /// minute height because the value used to calculate the start - /// position of the appointment. - xPosition -= inBetweenMinuteHeight; - } else { - xPosition += inBetweenMinuteHeight; - } - } - paint.color = appointment.color; - double width = 0; - if (view == CalendarView.timelineWorkWeek) { - totalHours = - endTime.hour - calendar.timeSlotViewSettings.startHour.toInt(); - minutes = endTime.minute - - ((calendar.timeSlotViewSettings.startHour - - calendar.timeSlotViewSettings.startHour.toInt()) * - 60); - totalMinutes = (totalHours * 60 + minutes).toInt(); - row = totalMinutes ~/ timeInterval; - - /// Calculate the in between minute height - /// Eg., If end time as 12.07 PM and time interval as 60 minutes - /// then the height holds the value of 07 minutes height. - double inBetweenMinuteHeight = - ((endTime.hour * 60 + endTime.minute) % timeInterval) * - minuteHeight; - if (isRTL) { - row = timeSlotCount - row; - - /// If the view direction as RTL then we subtract the in between - /// minute height because the value used to calculate the end - /// position of the appointment. - inBetweenMinuteHeight = -inBetweenMinuteHeight; - } - final double endXPosition = - (endColumn * viewWidth) + (row * cellWidth) + inBetweenMinuteHeight; - if (isRTL) { - width = xPosition - endXPosition; - } else { - width = endXPosition - xPosition; - } - } else { - final Duration difference = endTime.difference(startTime); - if (view != CalendarView.timelineMonth) { - /// The width for the appointment UI, calculated based on the minutes - /// difference between the start and end time of the appointment. - width = difference.inMinutes * minuteHeight; - } else { - /// The width for the appointment UI, calculated based on the date - /// difference between the start and end time of the appointment. - width = (difference.inDays + 1) * cellWidth; - - /// For span appointment less than 23 hours the difference will fall - /// as 0 hence to render the appointment on the next day, added one - /// the width for next day. - if (difference.inDays == 0 && endTime.day != startTime.day) { - width += cellWidth; - } - } - } - - if (calendar.timeSlotViewSettings.minimumAppointmentDuration != null && - calendar.timeSlotViewSettings.minimumAppointmentDuration.inMinutes > - 0 && - view != CalendarView.timelineMonth) { - final double minWidth = _getAppointmentHeightFromDuration( - calendar.timeSlotViewSettings.minimumAppointmentDuration, - calendar, - timeIntervalHeight); - width = width > minWidth ? width : minWidth; - } - - final Radius cornerRadius = Radius.circular( - (appointmentHeight * 0.1) > 2 ? 2 : (appointmentHeight * 0.1)); - final RRect rect = RRect.fromRectAndRadius( - Rect.fromLTWH(isRTL ? xPosition - width : xPosition, yPosition, - width - 1, appointmentHeight - 1), - cornerRadius); - appointmentView.appointmentRect = rect; - canvas.drawRRect(rect, paint); + canvas.drawRRect(appointmentView.appointmentRect, paint); final bool canAddSpanIcon = _canAddSpanIcon(visibleDates, appointment, view); bool canAddForwardIcon = false; bool canAddBackwardIcon = false; - double maxWidth = width - textStartPadding - 2; + double xPosition = isRTL + ? appointmentView.appointmentRect.right + : appointmentView.appointmentRect.left; + double maxWidth = + appointmentView.appointmentRect.width - textStartPadding; maxWidth = maxWidth > 0 ? maxWidth : 0; if (canAddSpanIcon) { @@ -1107,7 +2033,7 @@ class _AppointmentPainter extends CustomPainter { final DateTime viewStartDate = _convertToStartTime(visibleDates[0]); final DateTime viewEndDate = _convertToEndTime(visibleDates[visibleDates.length - 1]); - double iconSize = _getTextSize(rect, + double iconSize = _getTextSize(appointmentView.appointmentRect, (calendar.appointmentTextStyle.fontSize * textScaleFactor)) + textStartPadding; if (_canAddForwardSpanIcon( @@ -1136,8 +2062,10 @@ class _AppointmentPainter extends CustomPainter { style: calendar.appointmentTextStyle, ); - _updateTextPainter(span); - final double totalHeight = appointmentHeight - textStartPadding - 2; + _textPainter = + _updateTextPainter(span, _textPainter, isRTL, _textScaleFactor); + final double totalHeight = + appointmentView.appointmentRect.height - textStartPadding - 2; _updatePainterMaxLines(totalHeight); /// In RTL, when the text wraps into multiple line the tine width is @@ -1155,7 +2083,7 @@ class _AppointmentPainter extends CustomPainter { _textPainter.height > totalHeight) { if (appointmentHoverPosition != null) { paint ??= Paint(); - _updateAppointmentHovering(rect, canvas, paint); + _updateAppointmentHovering(appointmentView.appointmentRect, canvas); } continue; } @@ -1168,32 +2096,64 @@ class _AppointmentPainter extends CustomPainter { xPosition -= _textPainter.width + textStartPadding + 2; } - _textPainter.paint(canvas, - Offset(xPosition + textStartPadding, yPosition + textStartPadding)); + _textPainter.paint( + canvas, + Offset(xPosition + textStartPadding, + appointmentView.appointmentRect.top + textStartPadding)); if (appointment.recurrenceRule != null && appointment.recurrenceRule.isNotEmpty) { _addRecurrenceIconForTimeline( - canvas, size, rect, maxWidth, cornerRadius, paint); + canvas, + size, + appointmentView.appointmentRect, + maxWidth, + appointmentView.appointmentRect.tlRadius, + paint, + useMobilePlatformUI); } if (canAddSpanIcon) { if (canAddForwardIcon && canAddBackwardIcon) { _addForwardSpanIconForTimeline( - canvas, size, rect, maxWidth, cornerRadius, paint); + canvas, + size, + appointmentView.appointmentRect, + maxWidth, + appointmentView.appointmentRect.tlRadius, + paint, + isMobilePlatform); _addBackwardSpanIconForTimeline( - canvas, size, rect, maxWidth, cornerRadius, paint); + canvas, + size, + appointmentView.appointmentRect, + maxWidth, + appointmentView.appointmentRect.tlRadius, + paint, + isMobilePlatform); } else if (canAddForwardIcon) { _addForwardSpanIconForTimeline( - canvas, size, rect, maxWidth, cornerRadius, paint); + canvas, + size, + appointmentView.appointmentRect, + maxWidth, + appointmentView.appointmentRect.tlRadius, + paint, + isMobilePlatform); } else { _addBackwardSpanIconForTimeline( - canvas, size, rect, maxWidth, cornerRadius, paint); + canvas, + size, + appointmentView.appointmentRect, + maxWidth, + appointmentView.appointmentRect.tlRadius, + paint, + isMobilePlatform); } } if (appointmentHoverPosition != null) { paint ??= Paint(); - _updateAppointmentHovering(rect, canvas, paint); + _updateAppointmentHovering(appointmentView.appointmentRect, canvas); } } } @@ -1220,7 +2180,7 @@ class _AppointmentPainter extends CustomPainter { } double _getYPositionForSpanIconInTimeline( - TextSpan icon, RRect rect, double xPadding) { + 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, @@ -1228,25 +2188,32 @@ class _AppointmentPainter extends CustomPainter { /// device. final double iconStartPosition = (_textPainter.height - (icon.style.fontSize * textScaleFactor) / 2) / 2; - return rect.top - iconStartPosition + (kIsWeb ? xPadding : 1); + return rect.top - iconStartPosition + (isMobilePlatform ? 1 : xPadding); } - void _addForwardSpanIconForTimeline(Canvas canvas, Size size, RRect rect, - double maxWidth, Radius cornerRadius, Paint paint) { + void _addForwardSpanIconForTimeline( + Canvas canvas, + Size size, + RRect rect, + double maxWidth, + Radius cornerRadius, + Paint paint, + bool isMobilePlatform) { final double xPadding = 2; final double textSize = _getTextSize(rect, calendar.appointmentTextStyle.fontSize); final TextSpan icon = _getSpanIcon( calendar.appointmentTextStyle.color, textSize, isRTL ? false : true); - _updateTextPainter(icon); + _textPainter = + _updateTextPainter(icon, _textPainter, isRTL, _textScaleFactor); _textPainter.layout(minWidth: 0, maxWidth: maxWidth); final double xPosition = isRTL ? rect.left + xPadding : rect.right - _textPainter.width - xPadding; - final double yPosition = - _getYPositionForSpanIconInTimeline(icon, rect, xPadding); + final double yPosition = _getYPositionForSpanIconInTimeline( + icon, rect, xPadding, isMobilePlatform); canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromLTRB(xPosition, rect.top + 1, @@ -1256,22 +2223,29 @@ class _AppointmentPainter extends CustomPainter { _textPainter.paint(canvas, Offset(xPosition, yPosition)); } - void _addBackwardSpanIconForTimeline(Canvas canvas, Size size, RRect rect, - double maxWidth, Radius cornerRadius, Paint paint) { + void _addBackwardSpanIconForTimeline( + Canvas canvas, + Size size, + RRect rect, + double maxWidth, + Radius cornerRadius, + Paint paint, + bool isMobilePlatform) { final double xPadding = 2; final double textSize = _getTextSize(rect, calendar.appointmentTextStyle.fontSize); final TextSpan icon = _getSpanIcon( calendar.appointmentTextStyle.color, textSize, isRTL ? true : false); - _updateTextPainter(icon); + _textPainter = + _updateTextPainter(icon, _textPainter, isRTL, _textScaleFactor); _textPainter.layout(minWidth: 0, maxWidth: maxWidth); final double xPosition = isRTL ? rect.right - _textPainter.width - xPadding : rect.left + xPadding; - final double yPosition = - _getYPositionForSpanIconInTimeline(icon, rect, xPadding); + final double yPosition = _getYPositionForSpanIconInTimeline( + icon, rect, xPadding, isMobilePlatform); canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromLTRB(xPosition, rect.top + 1, @@ -1281,9 +2255,15 @@ class _AppointmentPainter extends CustomPainter { _textPainter.paint(canvas, Offset(xPosition, yPosition)); } - void _addRecurrenceIconForTimeline(Canvas canvas, Size size, RRect rect, - double maxWidth, Radius cornerRadius, Paint paint) { - final double xPadding = kIsWeb && size.width > _kMobileViewWidth ? 2 : 1; + void _addRecurrenceIconForTimeline( + Canvas canvas, + Size size, + RRect rect, + double maxWidth, + Radius cornerRadius, + Paint paint, + bool useMobilePlatformUI) { + final double xPadding = useMobilePlatformUI ? 1 : 2; const double bottomPadding = 2; final double textSize = _getTextSize(rect, calendar.appointmentTextStyle.fontSize); @@ -1306,61 +2286,4 @@ class _AppointmentPainter extends CustomPainter { Offset(isRTL ? rect.left + xPadding : rect.right - textSize - xPadding, rect.bottom - bottomPadding - textSize)); } - - void _updateAppointmentHovering(RRect rect, Canvas canvas, Paint paint) { - if (rect.left < appointmentHoverPosition.dx && - rect.right > appointmentHoverPosition.dx && - rect.top < appointmentHoverPosition.dy && - rect.bottom > appointmentHoverPosition.dy) { - paint.color = calendarTheme.selectionBorderColor.withOpacity(0.4); - paint.strokeWidth = 2; - paint.style = PaintingStyle.stroke; - canvas.drawRect(rect.outerRect, paint); - paint.style = PaintingStyle.fill; - } - } - - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the - /// list of custom painter semantics which contains the rect area and the - /// semantics properties for accessibility - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilder(size); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _AppointmentPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.visibleAppointments != visibleAppointments; - } - - List _getSemanticsBuilder(Size size) { - final List semanticsBuilder = - []; - if (_appointmentCollection == null || _appointmentCollection.isEmpty) { - return semanticsBuilder; - } - - for (int i = 0; i < _appointmentCollection.length; i++) { - if (_appointmentCollection[i].appointment == null) { - return semanticsBuilder; - } - - semanticsBuilder.add(CustomPainterSemantics( - rect: _appointmentCollection[i].appointmentRect == null - ? const Rect.fromLTWH(0, 0, 10, 10) - : _appointmentCollection[i].appointmentRect?.outerRect, - properties: SemanticsProperties( - label: _getAppointmentText(_appointmentCollection[i].appointment), - textDirection: TextDirection.ltr, - ), - )); - } - - return semanticsBuilder; - } } 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 c97a2184e..74761a5fb 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 @@ -5,9 +5,19 @@ bool _isRTLLayout(BuildContext context) { return direction != null && direction == TextDirection.rtl; } -/// Check the schedule view web needs web UI or not. -bool _isScheduleWebUI(double width) { - return kIsWeb && width > _kMobileViewWidth; +/// Determine the current platform needs mobile platform UI. +/// The [_kMobileViewWidth] value is a breakpoint for mobile platform. +bool _isMobileLayoutUI(double width, bool isMobileLayout) { + return isMobileLayout || width <= _kMobileViewWidth; +} + +/// Determine the current platform is mobile platform(android or iOS). +bool _isMobileLayout(TargetPlatform platform) { + if (kIsWeb) { + return false; + } + + return platform == TargetPlatform.android || platform == TargetPlatform.iOS; } /// Check the list is empty or not. @@ -43,6 +53,9 @@ Size _getTextWidgetWidth( text, style: style, maxLines: 1, + softWrap: false, + textDirection: TextDirection.ltr, + textAlign: TextAlign.left, ).build(context); /// Create and layout the render object based on allocated width and height. @@ -55,14 +68,18 @@ Size _getTextWidgetWidth( )); /// Get the size of text by using render object. - final TextBox lastBox = renderObject - .getBoxesForSelection( - TextSelection(baseOffset: 0, extentOffset: text.length)) - .last; + final List textBox = renderObject.getBoxesForSelection( + TextSelection(baseOffset: 0, extentOffset: text.length)); + double textWidth = 0; + double textHeight = 0; + for (final TextBox box in textBox) { + textWidth += box.right - box.left; + final double currentBoxHeight = box.bottom - box.top; + textHeight = textHeight > currentBoxHeight ? textHeight : currentBoxHeight; + } /// 10 padding added for text box(left and right side both as 5). - return Size( - (lastBox.right - lastBox.left) + 10, (lastBox.bottom - lastBox.top) + 10); + return Size(textWidth + 10, textHeight + 10); } Map _getCalendarViewsText(SfLocalizations localizations) { @@ -101,11 +118,11 @@ DateTime _getMonthEndDate(DateTime date) { /// Return day label width based on schedule view setting. double _getAgendaViewDayLabelWidth( - ScheduleViewSettings scheduleViewSettings, double width) { + ScheduleViewSettings scheduleViewSettings, bool useMobilePlatformUI) { if (scheduleViewSettings == null || scheduleViewSettings.dayHeaderSettings == null || scheduleViewSettings.dayHeaderSettings.width == -1) { - return (kIsWeb && width > _kMobileViewWidth) ? 150 : 50; + return useMobilePlatformUI ? 50 : 150; } return scheduleViewSettings.dayHeaderSettings.width; @@ -159,6 +176,34 @@ bool _isDateCollectionEqual( return true; } +/// Check both the collections are equal or not. +bool _isCollectionEqual(List collection1, List collection2) { + if (collection1 == collection2) { + return true; + } + + if (_isEmptyList(collection1) && _isEmptyList(collection2)) { + return true; + } + + if (collection1 == null || collection2 == null) { + return false; + } + + final int collectionCount = collection1.length; + if (collectionCount != collection2.length) { + return false; + } + + for (int i = 0; i < collectionCount; i++) { + if (collection1[i] != collection2[i]) { + return false; + } + } + + return true; +} + /// Check both the resource collection resources are equal or not. bool _isResourceCollectionEqual(List originalCollection, List copyCollection) { @@ -505,15 +550,14 @@ bool _isAutoTimeIntervalHeight(SfCalendar calendar, CalendarView view) { } /// Returns the default time interval width for timeline views. -double _getTimeIntervalWidth( - double timeIntervalHeight, CalendarView view, double width) { +double _getTimeIntervalWidth(double timeIntervalHeight, CalendarView view, + double width, bool isMobilePlatform) { if (timeIntervalHeight >= 0) { return timeIntervalHeight; } if (view == CalendarView.timelineMonth && - kIsWeb && - width > _kMobileViewWidth) { + !_isMobileLayoutUI(width, isMobilePlatform)) { return 160; } @@ -522,12 +566,18 @@ double _getTimeIntervalWidth( /// 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) { +double _getTimeIntervalHeight( + SfCalendar calendar, + CalendarView view, + double width, + double height, + int visibleDatesCount, + double allDayHeight, + bool isMobilePlatform) { final bool isTimelineView = _isTimelineView(view); double timeIntervalHeight = isTimelineView - ? _getTimeIntervalWidth( - calendar.timeSlotViewSettings.timeIntervalWidth, view, width) + ? _getTimeIntervalWidth(calendar.timeSlotViewSettings.timeIntervalWidth, + view, width, isMobilePlatform) : calendar.timeSlotViewSettings.timeIntervalHeight; if (!_isAutoTimeIntervalHeight(calendar, view)) { 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 20b8947bc..dad92986f 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/enums.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/enums.dart @@ -158,6 +158,14 @@ enum CalendarElement { /// appointment display mode as the appointment and the platform /// on the web page. moreAppointmentRegion, + + /// - CalendarElement.resourceHeader, Indicates that the region in the + /// resource panel, the view is on left side of timeline views, to display the + /// assigned resources in view. + /// + /// _Note: _This element applies to the timeline views with resource assigned + /// to the calendar data source. + resourceHeader } /// Action performed in data source 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 56dc1a7de..b8a7d08b2 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 @@ -80,6 +80,14 @@ typedef ScheduleViewMonthHeaderBuilder = Widget Function( typedef MonthCellBuilder = Widget Function( BuildContext context, MonthCellDetails details); +/// Signature for a function that creates a widget based on appointment details. +typedef CalendarAppointmentBuilder = Widget Function(BuildContext context, + CalendarAppointmentDetails calendarAppointmentDetails); + +/// Signature for a function that creates a widget based on time region details. +typedef TimeRegionBuilder = Widget Function( + BuildContext context, TimeRegionDetails timeRegionDetails); + class _CalendarParentData extends ContainerBoxParentData {} /// Contains the details that needed on month cell builder. @@ -113,6 +121,48 @@ class ScheduleViewMonthHeaderDetails { final Rect bounds; } +/// Contains the details that needed on appointment view builder. +class CalendarAppointmentDetails { + /// Default constructor to store the details needed in appointment builder. + const CalendarAppointmentDetails( + {this.date, + this.appointments, + this.bounds, + this.isMoreAppointmentRegion = false}); + + /// The date value associated with the appointment view widget. + final DateTime date; + + /// The position and size of the widget. + final Rect bounds; + + /// The appointment details associated with the appointment view widget. + /// It holds more appointments when it is more appointment + /// region [All day panel and Month cell more region]. + final Iterable appointments; + + /// Determines whether the widget replaces the more appointment region. + /// It is applicable on the day, week, workweek views all day panel and + /// month cell appointment. + final bool isMoreAppointmentRegion; +} + +/// Contains the details that needed on special region view builder. +class TimeRegionDetails { + /// Default constructor to store the details needed in time region builder. + TimeRegionDetails({this.region, this.date, this.bounds}); + + /// Region detail associated with the time region view in day, week, + /// workweek and timeline day, week, workweek views. + final TimeRegion region; + + /// Date value associated with the time region. + final DateTime date; + + /// Position and size of the time region view widget. + final Rect bounds; +} + /// args to update the required properties from calendar state to it's /// children's class _UpdateCalendarStateDetails { 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 1f9c5c805..6afc5088e 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 @@ -9,7 +9,8 @@ class _ResourceContainer extends CustomPainter { this.calendarTheme, this.notifier, this.isRTL, - this.textScaleFactor) + this.textScaleFactor, + this.mouseHoverPosition) : super(repaint: notifier); final List resources; @@ -20,6 +21,7 @@ class _ResourceContainer extends CustomPainter { final ValueNotifier notifier; final bool isRTL; final double textScaleFactor; + final Offset mouseHoverPosition; Paint _circlePainter; TextPainter _namePainter; final double _borderThickness = 5; @@ -64,6 +66,11 @@ class _ResourceContainer extends CustomPainter { _circlePainter.style = PaintingStyle.stroke; canvas.drawLine(Offset(0, yPosition), Offset(size.width, yPosition), _circlePainter); + + if (mouseHoverPosition != null) { + _addHovering(canvas, size, yPosition); + } + yPosition += resourceItemHeight; canvas.restore(); } @@ -73,11 +80,31 @@ class _ResourceContainer extends CustomPainter { _drawResourceBackground(canvas, size, resource, yPosition); _drawDisplayName( resource, canvas, size, yPosition, actualItemHeight, radius); + if (mouseHoverPosition != null) { + _addHovering(canvas, size, yPosition); + } yPosition += resourceItemHeight; } } } + void _addHovering(Canvas canvas, Size size, double yPosition) { + _circlePainter ??= Paint(); + if (mouseHoverPosition.dy > yPosition && + mouseHoverPosition.dy < (yPosition + resourceItemHeight)) { + _circlePainter.style = PaintingStyle.fill; + _circlePainter.color = (calendarTheme.brightness != null && + calendarTheme.brightness == Brightness.dark + ? Colors.white + : Colors.black87) + .withOpacity(0.04); + final double padding = 0.5; + canvas.drawRect( + Rect.fromLTWH(0, yPosition, size.width, resourceItemHeight - padding), + _circlePainter); + } + } + void _drawResourceBackground( Canvas canvas, Size size, CalendarResource resource, double yPosition) { final double padding = 0.5; @@ -228,7 +255,8 @@ class _ResourceContainer extends CustomPainter { return oldWidget.resourceItemHeight != resourceItemHeight || oldWidget.resources != resources || oldWidget.resourceViewSettings != resourceViewSettings || - oldWidget._isImageLoaded != _isImageLoaded; + oldWidget._isImageLoaded != _isImageLoaded || + oldWidget.mouseHoverPosition != mouseHoverPosition; } List _getSemanticsBuilder(Size size) { diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/scroll_view/custom_scroll_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/scroll_view/custom_scroll_view.dart index bb4e57d7d..72cd14316 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/scroll_view/custom_scroll_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/scroll_view/custom_scroll_view.dart @@ -7,7 +7,6 @@ class _CustomScrollView extends StatefulWidget { this.view, this.width, this.height, - this.visibleAppointments, this.agendaSelectedDate, this.isRTL, this.locale, @@ -18,6 +17,8 @@ class _CustomScrollView extends StatefulWidget { this.removePicker, this.resourcePanelScrollController, this.textScaleFactor, + this.isMobilePlatform, + this.fadeInController, {this.updateCalendarState, this.getCalendarState}); @@ -33,11 +34,12 @@ class _CustomScrollView extends StatefulWidget { final VoidCallback removePicker; final _UpdateCalendarState getCalendarState; final ValueNotifier agendaSelectedDate; - final List visibleAppointments; final List specialRegions; final ScrollController resourcePanelScrollController; final double textScaleFactor; + final bool isMobilePlatform; final List blackoutDates; + final AnimationController fadeInController; @override _CustomScrollViewState createState() => _CustomScrollViewState(); @@ -93,6 +95,21 @@ class _CustomScrollViewState extends State<_CustomScrollView> /// manipulations(add, remove, reset). List _resourceCollection; + /// The variable stores the timeline view scroll start position used to + /// decide the scroll as timeline scroll or scroll view on scroll update. + double _timelineScrollStartPosition = 0; + + /// The variable used to store the scroll start position to calculate the + /// scroll difference on scroll update. + double _timelineStartPosition = 0; + + /// Boolean value used to trigger the horizontal end animation when user + /// stops the scroll at middle. + bool _isNeedTimelineScrollEnd = false; + + /// Used to perform the drag or scroll in timeline view. + Drag _drag; + FocusNode _focusNode; @override @@ -118,7 +135,10 @@ class _CustomScrollViewState extends State<_CustomScrollView> vsync: this, animationBehavior: AnimationBehavior.normal); _tween = Tween(begin: 0.0, end: 0.1); - _animation = _tween.animate(_animationController) + _animation = _tween.animate(CurvedAnimation( + parent: _animationController, + curve: Curves.ease, + )) ..addListener(animationListener); _timeRegions = _cloneList(widget.specialRegions); @@ -148,6 +168,16 @@ class _CustomScrollViewState extends State<_CustomScrollView> if (oldWidget.view != widget.view) { _children.clear(); + + /// Switching timeline view from non timeline view or non timeline view + /// from timeline view creates the scroll layout as new because we handle + /// the scrolling touch for timeline view in this widget, so current + /// widget tree differ on timeline and non timeline views, so it creates + /// new widget tree. + if (_isTimelineView(widget.view) != _isTimelineView(oldWidget.view)) { + _currentChildIndex = 1; + } + _updateVisibleDates(); _position = 0; } @@ -210,6 +240,26 @@ class _CustomScrollViewState extends State<_CustomScrollView> oldWidget.locale != widget.locale || oldWidget.calendar.selectionDecoration != widget.calendar.selectionDecoration) { + final bool isTimelineView = _isTimelineView(widget.view); + if (widget.view != CalendarView.month && + (oldWidget.calendar.timeSlotViewSettings.timeInterval != + widget.calendar.timeSlotViewSettings.timeInterval || + (!isTimelineView && + oldWidget.calendar.timeSlotViewSettings.timeIntervalHeight != + widget + .calendar.timeSlotViewSettings.timeIntervalHeight) || + (isTimelineView && + oldWidget.calendar.timeSlotViewSettings.timeIntervalWidth != + widget + .calendar.timeSlotViewSettings.timeIntervalWidth))) { + if (_currentChildIndex == 0) { + _previousViewKey.currentState._retainScrolledDateTime(); + } else if (_currentChildIndex == 1) { + _currentViewKey.currentState._retainScrolledDateTime(); + } else if (_currentChildIndex == 2) { + _nextViewKey.currentState._retainScrolledDateTime(); + } + } _children.clear(); _position = 0; } @@ -296,59 +346,74 @@ class _CustomScrollViewState extends State<_CustomScrollView> bottomPosition = bottomPosition ?? -widget.height; } + final bool isTimelineView = _isTimelineView(widget.view); + final Widget customScrollWidget = GestureDetector( + child: CustomScrollViewerLayout( + _addViews(), + widget.view != CalendarView.month || + widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.horizontal + ? CustomScrollDirection.horizontal + : CustomScrollDirection.vertical, + _position, + _currentChildIndex), + onTapDown: (TapDownDetails details) { + if (!_focusNode.hasFocus) { + _focusNode.requestFocus(); + } + }, + onHorizontalDragStart: isTimelineView ? null : _onHorizontalStart, + onHorizontalDragUpdate: isTimelineView ? null : _onHorizontalUpdate, + onHorizontalDragEnd: isTimelineView ? null : _onHorizontalEnd, + onVerticalDragStart: widget.view == CalendarView.month && + widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical + ? _onVerticalStart + : null, + onVerticalDragUpdate: widget.view == CalendarView.month && + widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical + ? _onVerticalUpdate + : null, + onVerticalDragEnd: widget.view == CalendarView.month && + widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical + ? _onVerticalEnd + : null, + ); + return Stack( children: [ Positioned( - left: leftPosition, - right: rightPosition, - bottom: bottomPosition, - top: topPosition, - child: RawKeyboardListener( - focusNode: _focusNode, - onKey: _onKeyDown, - child: GestureDetector( - child: CustomScrollViewerLayout( - _addViews(), - widget.view != CalendarView.month || - widget.calendar.monthViewSettings - .navigationDirection == - MonthNavigationDirection.horizontal - ? CustomScrollDirection.horizontal - : CustomScrollDirection.vertical, - _position, - _currentChildIndex), - onTapDown: (TapDownDetails details) { - if (!_focusNode.hasFocus) { - _focusNode.requestFocus(); - } - }, - onHorizontalDragStart: (DragStartDetails details) { - _onHorizontalStart(details); - }, - onHorizontalDragUpdate: (DragUpdateDetails details) { - _onHorizontalUpdate(details); - }, - onHorizontalDragEnd: (DragEndDetails details) { - _onHorizontalEnd(details); - }, - onVerticalDragStart: widget.view == CalendarView.month && - widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical - ? _onVerticalStart - : null, - onVerticalDragUpdate: widget.view == CalendarView.month && - widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical - ? _onVerticalUpdate - : null, - onVerticalDragEnd: widget.view == CalendarView.month && - widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical - ? _onVerticalEnd - : null, - ), - ), - ), + left: leftPosition, + right: rightPosition, + bottom: bottomPosition, + top: topPosition, + child: RawKeyboardListener( + focusNode: _focusNode, + onKey: _onKeyDown, + child: isTimelineView + ? Listener( + onPointerSignal: _handlePointerSignal, + child: RawGestureDetector( + gestures: { + HorizontalDragGestureRecognizer: + GestureRecognizerFactoryWithHandlers< + HorizontalDragGestureRecognizer>( + () => HorizontalDragGestureRecognizer(), + (HorizontalDragGestureRecognizer instance) { + instance..onUpdate = _handleDragUpdate; + instance..onStart = _handleDragStart; + instance..onEnd = _handleDragEnd; + instance..onCancel = _handleDragCancel; + }, + ) + }, + behavior: HitTestBehavior.opaque, + child: customScrollWidget), + ) + : customScrollWidget, + )), ], ); } @@ -367,6 +432,164 @@ class _CustomScrollViewState extends State<_CustomScrollView> super.dispose(); } + /// Get the scroll layout current child view state based on its visible dates. + GlobalKey<_CalendarViewState> _getCurrentViewByVisibleDates() { + _CalendarView view; + for (int i = 0; i < _children.length; i++) { + final _CalendarView currentView = _children[i]; + if (currentView.visibleDates == _currentViewVisibleDates) { + view = currentView; + break; + } + } + + if (view == null) { + return null; + } + return view.key; + } + + /// Handle start of the scroll, set the scroll start position and check + /// the start position as start or end of timeline scroll controller. + /// If the timeline view scroll starts at min or max scroll position then + /// move the previous view to end of the scroll or move the next view to + /// start of the scroll and set the drag as timeline scroll controller drag. + void _handleDragStart(DragStartDetails details) { + if (!_isTimelineView(widget.view)) { + return; + } + final GlobalKey<_CalendarViewState> viewKey = + _getCurrentViewByVisibleDates(); + _timelineScrollStartPosition = + viewKey.currentState._scrollController.position.pixels; + _timelineStartPosition = details.globalPosition.dx; + _isNeedTimelineScrollEnd = false; + + /// If the timeline view scroll starts at min or max scroll position then + /// move the previous view to end of the scroll or move the next view to + /// start of the scroll + if (_timelineScrollStartPosition >= + viewKey.currentState._scrollController.position.maxScrollExtent) { + _positionTimelineView(); + } else if (_timelineScrollStartPosition <= + viewKey.currentState._scrollController.position.minScrollExtent) { + _positionTimelineView(); + } + + /// Set the drag as timeline scroll controller drag. + if (viewKey.currentState._scrollController.hasClients && + viewKey.currentState._scrollController.position != null) { + _drag = viewKey.currentState._scrollController.position + .drag(details, _disposeDrag); + } + } + + /// Handles the scroll update, if the scroll moves after the timeline max + /// scroll position or before the timeline min scroll position then check the + /// scroll start position if it is start or end of the timeline scroll view + /// then pass the touch to custom scroll view and set the timeline view + /// drag as null; + void _handleDragUpdate(DragUpdateDetails details) { + if (!_isTimelineView(widget.view)) { + return; + } + final GlobalKey<_CalendarViewState> viewKey = + _getCurrentViewByVisibleDates(); + + /// Calculate the scroll difference by current scroll position and start + /// scroll position. + final double difference = + details.globalPosition.dx - _timelineStartPosition; + if (_timelineScrollStartPosition >= + viewKey.currentState._scrollController.position.maxScrollExtent && + ((difference < 0 && !widget.isRTL) || + (difference > 0 && widget.isRTL))) { + /// Set the scroll position as timeline scroll start position and the + /// value used on horizontal update method. + _scrollStartPosition = _timelineStartPosition; + _drag?.cancel(); + + /// Move the touch(drag) to custom scroll view. + _onHorizontalUpdate(details); + + /// Enable boolean value used to trigger the horizontal end animation on + /// drag end. + _isNeedTimelineScrollEnd = true; + + /// Remove the timeline view drag or scroll. + _disposeDrag(); + return; + } else if (_timelineScrollStartPosition <= + viewKey.currentState._scrollController.position.minScrollExtent && + ((difference > 0 && !widget.isRTL) || + (difference < 0 && widget.isRTL))) { + /// Set the scroll position as timeline scroll start position and the + /// value used on horizontal update method. + _scrollStartPosition = _timelineStartPosition; + _drag?.cancel(); + + /// Move the touch(drag) to custom scroll view. + _onHorizontalUpdate(details); + + /// Enable boolean value used to trigger the horizontal end animation on + /// drag end. + _isNeedTimelineScrollEnd = true; + + /// Remove the timeline view drag or scroll. + _disposeDrag(); + return; + } + + _drag?.update(details); + } + + /// Handle the scroll end to update the timeline view scroll or custom scroll + /// view scroll based on [_isNeedTimelineScrollEnd] value + void _handleDragEnd(DragEndDetails details) { + if (_isNeedTimelineScrollEnd) { + _isNeedTimelineScrollEnd = false; + _onHorizontalEnd(details); + return; + } + + _isNeedTimelineScrollEnd = false; + _drag?.end(details); + } + + /// Handle drag cancel related operations. + void _handleDragCancel() { + _isNeedTimelineScrollEnd = false; + _drag?.cancel(); + } + + /// Remove the drag when the touch(drag) passed to custom scroll view. + void _disposeDrag() { + _drag = null; + } + + /// Handle the pointer scroll when a pointer signal occurs over this object. + /// eg., track pad scroll. + void _handlePointerSignal(PointerSignalEvent event) { + final GlobalKey<_CalendarViewState> viewKey = + _getCurrentViewByVisibleDates(); + if (event is PointerScrollEvent && + viewKey.currentState._scrollController.position != null) { + final double scrolledPosition = + widget.isRTL ? -event.scrollDelta.dx : event.scrollDelta.dx; + final double targetScrollOffset = math.min( + math.max( + viewKey.currentState._scrollController.position.pixels + + scrolledPosition, + viewKey.currentState._scrollController.position.minScrollExtent), + viewKey.currentState._scrollController.position.maxScrollExtent); + if (targetScrollOffset != + viewKey.currentState._scrollController.position.pixels) { + viewKey.currentState._scrollController.position + .jumpTo(targetScrollOffset); + } + } + } + void _updateVisibleDates() { widget.getCalendarState(_updateCalendarStateDetails); final DateTime currentDate = DateTime( @@ -555,6 +778,7 @@ class _CustomScrollViewState extends State<_CustomScrollView> widget.resourcePanelScrollController, _resourceCollection, widget.textScaleFactor, + widget.isMobilePlatform, key: _previousViewKey, updateCalendarState: (_UpdateCalendarStateDetails details) { _updateCalendarViewStateDetails(details); @@ -581,6 +805,7 @@ class _CustomScrollViewState extends State<_CustomScrollView> widget.resourcePanelScrollController, _resourceCollection, widget.textScaleFactor, + widget.isMobilePlatform, key: _currentViewKey, updateCalendarState: (_UpdateCalendarStateDetails details) { _updateCalendarViewStateDetails(details); @@ -608,6 +833,7 @@ class _CustomScrollViewState extends State<_CustomScrollView> widget.resourcePanelScrollController, _resourceCollection, widget.textScaleFactor, + widget.isMobilePlatform, key: _nextViewKey, updateCalendarState: (_UpdateCalendarStateDetails details) { _updateCalendarViewStateDetails(details); @@ -623,6 +849,7 @@ class _CustomScrollViewState extends State<_CustomScrollView> return _children; } + widget.getCalendarState(_updateCalendarStateDetails); final _CalendarView previousView = _updateViews( _previousView, _previousViewKey, _previousViewVisibleDates); final _CalendarView currentView = @@ -650,6 +877,8 @@ class _CustomScrollViewState extends State<_CustomScrollView> GlobalKey<_CalendarViewState> viewKey, List visibleDates) { final int index = _children.indexOf(view); + final _AppointmentLayout appointmentLayout = + viewKey.currentState._appointmentLayoutKey.currentWidget; // update the view with the visible dates on swiping end. if (view.visibleDates != visibleDates) { view = _CalendarView( @@ -670,6 +899,7 @@ class _CustomScrollViewState extends State<_CustomScrollView> widget.resourcePanelScrollController, _resourceCollection, widget.textScaleFactor, + widget.isMobilePlatform, key: viewKey, updateCalendarState: (_UpdateCalendarStateDetails details) { _updateCalendarViewStateDetails(details); @@ -681,9 +911,8 @@ class _CustomScrollViewState extends State<_CustomScrollView> _children[index] = view; } // check and update the visible appointments in the view - else if (viewKey.currentState._appointmentPainter != null && - viewKey.currentState._appointmentPainter.visibleAppointments != - widget.visibleAppointments) { + else if (!_isCollectionEqual(appointmentLayout.visibleAppointments.value, + _updateCalendarStateDetails._visibleAppointments)) { if (widget.view != CalendarView.month && !_isTimelineView(widget.view)) { view = _CalendarView( widget.calendar, @@ -703,6 +932,7 @@ class _CustomScrollViewState extends State<_CustomScrollView> widget.resourcePanelScrollController, _resourceCollection, widget.textScaleFactor, + widget.isMobilePlatform, key: viewKey, updateCalendarState: (_UpdateCalendarStateDetails details) { _updateCalendarViewStateDetails(details); @@ -713,22 +943,12 @@ class _CustomScrollViewState extends State<_CustomScrollView> ); _children[index] = view; } else if (view.visibleDates == _currentViewVisibleDates) { + appointmentLayout.visibleAppointments.value = + _updateCalendarStateDetails._visibleAppointments; if (widget.view == CalendarView.month && widget.calendar.monthCellBuilder != null) { - viewKey.currentState._appointmentPainter.visibleAppointments = - widget.visibleAppointments; - if (!_isEmptyList(viewKey - .currentState._monthView.visibleAppointmentNotifier.value) || - !_isEmptyList(widget.visibleAppointments)) { - viewKey.currentState._monthView.visibleAppointmentNotifier.value = - widget.visibleAppointments; - } - } else { - viewKey.currentState._appointmentPainter.visibleAppointments = - widget.visibleAppointments; - viewKey.currentState._appointmentPainter.calendar = widget.calendar; - viewKey.currentState._appointmentNotifier.value = - !viewKey.currentState._appointmentNotifier.value; + viewKey.currentState._monthView.visibleAppointmentNotifier.value = + _updateCalendarStateDetails._visibleAppointments; } } } @@ -756,6 +976,7 @@ class _CustomScrollViewState extends State<_CustomScrollView> widget.resourcePanelScrollController, _resourceCollection, widget.textScaleFactor, + widget.isMobilePlatform, key: viewKey, updateCalendarState: (_UpdateCalendarStateDetails details) { _updateCalendarViewStateDetails(details); @@ -812,8 +1033,13 @@ class _CustomScrollViewState extends State<_CustomScrollView> for (int i = 0; i < _children.length; i++) { final GlobalKey<_CalendarViewState> viewKey = _children[i].key; - viewKey.currentState._selectedResourceIndex = 0; - viewKey.currentState._selectionPainter.selectedResourceIndex = 0; + if (_isResourceEnabled(widget.calendar.dataSource, widget.view)) { + viewKey.currentState._selectedResourceIndex = 0; + viewKey.currentState._selectionPainter.selectedResourceIndex = 0; + } else { + viewKey.currentState._selectedResourceIndex = -1; + viewKey.currentState._selectionPainter.selectedResourceIndex = -1; + } } } @@ -953,24 +1179,17 @@ class _CustomScrollViewState extends State<_CustomScrollView> _updateAllDayPanel(); } - if (_currentChildIndex == 0) { - _currentChildIndex = 1; - } else if (_currentChildIndex == 1) { - _currentChildIndex = 2; - } else if (_currentChildIndex == 2) { - _currentChildIndex = 0; - } - - if (kIsWeb) { - setState(() { - /// set state called to call the build method to fix the date doesn't - /// update properly issue on web, in Andriod and iOS the build method - /// called automatically when the animation ends but in web it doesn't - /// work on that way, hence we have manually called the build method by - /// adding setstate and i have logged and issue in framework once i got - /// the solution will remove this setstate - }); - } + setState(() { + /// Update the custom scroll layout current child index when the + /// animation ends. + if (_currentChildIndex == 0) { + _currentChildIndex = 1; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 0; + } + }); _resetPosition(); _updateAppointmentPainter(); @@ -993,29 +1212,28 @@ class _CustomScrollViewState extends State<_CustomScrollView> _updateAllDayPanel(); } - if (_currentChildIndex == 0) { - _currentChildIndex = 2; - } else if (_currentChildIndex == 1) { - _currentChildIndex = 0; - } else if (_currentChildIndex == 2) { - _currentChildIndex = 1; - } + setState(() { + /// Update the custom scroll layout current child index when the + /// animation ends. + if (_currentChildIndex == 0) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 0; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 1; + } + }); - if (kIsWeb) { - setState(() { - /// set state called to call the build method to fix the date doesn't - /// update properly issue on web, in Android and iOS the build method - /// called automatically when the animation ends but in web it doesn't - /// work on that way, hence we have manually called the build method by - /// adding 'setState' and i have logged and issue in framework once i - /// got the solution will remove this 'setState' - }); - } _resetPosition(); _updateAppointmentPainter(); } void _moveToNextViewWithAnimation() { + if (!widget.isMobilePlatform) { + _moveToNextWebViewWithAnimation(); + return; + } + if (!_canMoveToNextView( widget.view, widget.calendar.monthViewSettings.numberOfWeeksInView, @@ -1053,7 +1271,7 @@ class _CustomScrollViewState extends State<_CustomScrollView> _tween.end = -widget.width; } - _animationController.duration = const Duration(milliseconds: 500); + _animationController.duration = const Duration(milliseconds: 250); _animationController .forward() .then((dynamic value) => _updateNextView()); @@ -1063,6 +1281,11 @@ class _CustomScrollViewState extends State<_CustomScrollView> } void _moveToPreviousViewWithAnimation() { + if (!widget.isMobilePlatform) { + _moveToPreviousWebViewWithAnimation(); + return; + } + if (!_canMoveToPreviousView( widget.view, widget.calendar.monthViewSettings.numberOfWeeksInView, @@ -1100,7 +1323,7 @@ class _CustomScrollViewState extends State<_CustomScrollView> _tween.end = widget.width; } - _animationController.duration = const Duration(milliseconds: 500); + _animationController.duration = const Duration(milliseconds: 250); _animationController .forward() .then((dynamic value) => _updatePreviousView()); @@ -1109,6 +1332,121 @@ class _CustomScrollViewState extends State<_CustomScrollView> _updateCurrentViewVisibleDates(); } + void _moveToPreviousWebViewWithAnimation() { + if (!_canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + return; + } + + // Resets the controller to backward it again, the animation will backward + // only from the dismissed state + if (widget.fadeInController.isCompleted || + widget.fadeInController.isDismissed) { + widget.fadeInController.reset(); + } else { + return; + } + + // Handled for time line view, to move the previous and next view to it's + // start and end position accordingly + if (_isTimelineView(widget.view)) { + _positionTimelineView(isScrolledToEnd: false); + } else if (!_isTimelineView(widget.view) && + widget.view != CalendarView.month) { + _updateDayViewScrollPosition(); + } + + /// updates the current view visible dates when the view swiped. + _updateCurrentViewVisibleDates(); + _position = 0; + widget.fadeInController.forward(); + _updateSelection(); + _updatePreviousViewVisibleDates(); + + /// Updates the all day panel of the view, when the all day panel expanded + /// and the view swiped with the expanded all day panel, and when we swipe + /// back to the view or swipes three times will render the all day panel as + /// expanded, to collapse the all day panel in day, week and work week view, + /// we have added this condition and called the method. + if (widget.view != CalendarView.month && !_isTimelineView(widget.view)) { + _updateAllDayPanel(); + } + + if (_currentChildIndex == 0) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 0; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 1; + } + + _updateAppointmentPainter(); + } + + void _moveToNextWebViewWithAnimation() { + if (!_canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + return; + } + + // Resets the controller to forward it again, the animation will forward + // only from the dismissed state + if (widget.fadeInController.isCompleted || + widget.fadeInController.isDismissed) { + widget.fadeInController.reset(); + } else { + return; + } + + // Handled for time line view, to move the previous and next view to it's + // start and end position accordingly + if (_isTimelineView(widget.view)) { + _positionTimelineView(isScrolledToEnd: false); + } else if (!_isTimelineView(widget.view) && + widget.view != CalendarView.month) { + _updateDayViewScrollPosition(); + } + + /// updates the current view visible dates when the view swiped + _updateCurrentViewVisibleDates(isNextView: true); + + _position = 0; + widget.fadeInController.forward(); + _updateSelection(); + _updateNextViewVisibleDates(); + + /// Updates the all day panel of the view, when the all day panel expanded + /// and the view swiped with the expanded all day panel, and when we swipe + /// back to the view or swipes three times will render the all day panel as + /// expanded, to collapse the all day panel in day, week and work week view, + /// we have added this condition and called the method. + if (widget.view != CalendarView.month && !_isTimelineView(widget.view)) { + _updateAllDayPanel(); + } + + if (_currentChildIndex == 0) { + _currentChildIndex = 1; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 0; + } + + _updateAppointmentPainter(); + } + // resets position to zero on the swipe end to avoid the unwanted date updates void _resetPosition() { SchedulerBinding.instance.addPostFrameCallback((_) { @@ -1135,42 +1473,43 @@ class _CustomScrollViewState extends State<_CustomScrollView> return; } - double scrolledPosition = 0; - if (_currentChildIndex == 0) { - scrolledPosition = - _previousViewKey.currentState._scrollController.offset; - } else if (_currentChildIndex == 1) { - scrolledPosition = - _currentViewKey.currentState._scrollController.offset; - } else if (_currentChildIndex == 2) { - scrolledPosition = _nextViewKey.currentState._scrollController.offset; - } + _updateDayViewScrollPosition(); + }); + } - if (_previousViewKey.currentState._scrollController.offset != - scrolledPosition && - _previousViewKey - .currentState._scrollController.position.maxScrollExtent >= - scrolledPosition) { - _previousViewKey.currentState._scrollController - .jumpTo(scrolledPosition); - } + /// Update the current day view view scroll position to other views. + void _updateDayViewScrollPosition() { + double scrolledPosition = 0; + if (_currentChildIndex == 0) { + scrolledPosition = _previousViewKey.currentState._scrollController.offset; + } else if (_currentChildIndex == 1) { + scrolledPosition = _currentViewKey.currentState._scrollController.offset; + } else if (_currentChildIndex == 2) { + scrolledPosition = _nextViewKey.currentState._scrollController.offset; + } - if (_currentViewKey.currentState._scrollController.offset != - scrolledPosition && - _currentViewKey - .currentState._scrollController.position.maxScrollExtent >= - scrolledPosition) { - _currentViewKey.currentState._scrollController.jumpTo(scrolledPosition); - } + if (_previousViewKey.currentState._scrollController.offset != + scrolledPosition && + _previousViewKey + .currentState._scrollController.position.maxScrollExtent >= + scrolledPosition) { + _previousViewKey.currentState._scrollController.jumpTo(scrolledPosition); + } - if (_nextViewKey.currentState._scrollController.offset != - scrolledPosition && - _nextViewKey - .currentState._scrollController.position.maxScrollExtent >= - scrolledPosition) { - _nextViewKey.currentState._scrollController.jumpTo(scrolledPosition); - } - }); + if (_currentViewKey.currentState._scrollController.offset != + scrolledPosition && + _currentViewKey + .currentState._scrollController.position.maxScrollExtent >= + scrolledPosition) { + _currentViewKey.currentState._scrollController.jumpTo(scrolledPosition); + } + + if (_nextViewKey.currentState._scrollController.offset != + scrolledPosition && + _nextViewKey.currentState._scrollController.position.maxScrollExtent >= + scrolledPosition) { + _nextViewKey.currentState._scrollController.jumpTo(scrolledPosition); + } } int _getRowOfDate( @@ -1723,21 +2062,6 @@ class _CustomScrollViewState extends State<_CustomScrollView> void _onHorizontalUpdate(DragUpdateDetails dragUpdateDetails) { widget.removePicker(); - // Handled for time line view to manually update the scroll position of the - // scroll view of time line view while pass the touch to the scroll view - if (_isTimelineView(widget.view)) { - _position = dragUpdateDetails.globalPosition.dx - _scrollStartPosition; - for (int i = 0; i < _children.length; i++) { - if (_children[i].visibleDates == _currentViewVisibleDates) { - final GlobalKey<_CalendarViewState> viewKey = _children[i].key; - if (viewKey.currentState._isUpdateTimelineViewScroll( - _scrollStartPosition, dragUpdateDetails.globalPosition.dx)) { - return; - } - break; - } - } - } if (widget.calendar.monthViewSettings.navigationDirection == MonthNavigationDirection.horizontal || @@ -1778,20 +2102,6 @@ class _CustomScrollViewState extends State<_CustomScrollView> void _onHorizontalEnd(DragEndDetails dragEndDetails) { widget.removePicker(); - // Handled for time line view to manually update the scroll position of the - // scroll view of time line view while pass the touch to the scroll view - if (_isTimelineView(widget.view)) { - for (int i = 0; i < _children.length; i++) { - if (_children[i].visibleDates == _currentViewVisibleDates) { - final GlobalKey<_CalendarViewState> viewKey = _children[i].key; - if (viewKey.currentState._isAnimateTimelineViewScroll( - _position, dragEndDetails.primaryVelocity)) { - return; - } - break; - } - } - } if (widget.calendar.monthViewSettings.navigationDirection == MonthNavigationDirection.horizontal || @@ -2133,11 +2443,12 @@ class _CustomScrollViewState extends State<_CustomScrollView> if (widget.view == CalendarView.month && widget.calendar.monthCellBuilder != null) { if (view.visibleDates == _currentViewVisibleDates) { - if (!_isEmptyList(viewKey - .currentState._monthView.visibleAppointmentNotifier.value) || - !_isEmptyList(widget.visibleAppointments)) { + widget.getCalendarState(_updateCalendarStateDetails); + if (!_isCollectionEqual( + viewKey.currentState._monthView.visibleAppointmentNotifier.value, + _updateCalendarStateDetails._visibleAppointments)) { viewKey.currentState._monthView.visibleAppointmentNotifier.value = - widget.visibleAppointments; + _updateCalendarStateDetails._visibleAppointments; } } else { if (!_isEmptyList(viewKey @@ -2147,8 +2458,20 @@ class _CustomScrollViewState extends State<_CustomScrollView> } } } else { - viewKey.currentState._appointmentNotifier.value = - !viewKey.currentState._appointmentNotifier.value; + final _AppointmentLayout appointmentLayout = + viewKey.currentState._appointmentLayoutKey.currentWidget; + if (view.visibleDates == _currentViewVisibleDates) { + widget.getCalendarState(_updateCalendarStateDetails); + if (!_isCollectionEqual(appointmentLayout.visibleAppointments.value, + _updateCalendarStateDetails._visibleAppointments)) { + appointmentLayout.visibleAppointments.value = + _updateCalendarStateDetails._visibleAppointments; + } + } else { + if (!_isEmptyList(appointmentLayout.visibleAppointments.value)) { + appointmentLayout.visibleAppointments.value = null; + } + } } } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart index a4a46ade4..3f8fbb4c3 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart @@ -161,6 +161,8 @@ class SfCalendar extends StatefulWidget { this.blackoutDates, this.scheduleViewMonthHeaderBuilder, this.monthCellBuilder, + this.appointmentBuilder, + this.timeRegionBuilder, CalendarHeaderStyle headerStyle, ViewHeaderStyle viewHeaderStyle, TimeSlotViewSettings timeSlotViewSettings, @@ -175,6 +177,7 @@ class SfCalendar extends StatefulWidget { bool showNavigationArrow, bool showDatePickerButton, bool allowViewNavigation, + double cellEndPadding, this.allowedViews, this.specialRegions, this.blackoutDatesTextStyle, @@ -211,6 +214,7 @@ class SfCalendar extends StatefulWidget { showNavigationArrow = showNavigationArrow ?? false, showDatePickerButton = showDatePickerButton ?? false, allowViewNavigation = allowViewNavigation ?? false, + cellEndPadding = cellEndPadding ?? 1, super(key: key); /// The list of [CalendarView]s that should be displayed in the header for @@ -383,6 +387,141 @@ class SfCalendar extends StatefulWidget { /// ``` final MonthCellBuilder monthCellBuilder; + /// A builder that builds a widget, replaces the appointment view in a day, + /// week, workweek, month, schedule and timeline day, week, workweek, + /// month views. + /// + /// Note: In month view, this builder callback will be used to build + /// appointment views for appointments displayed in both month cell and + /// agenda views when the MonthViewSettings.appointmentDisplayMode is + /// set to appointment. + /// + /// ```dart + /// CalendarController _controller; + /// + /// @override + /// void initState() { + /// _controller = CalendarController(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfCalendar( + /// view: CalendarView.day, + /// controller: _controller, + /// appointmentBuilder: (BuildContext context, + /// CalendarAppointmentDetails calendarAppointmentDetails) { + /// if (calendarAppointmentDetails.isMoreAppointmentRegion) { + /// return Container( + /// width: calendarAppointmentDetails.bounds.width, + /// height: calendarAppointmentDetails.bounds.height, + /// child: Text('+More'), + /// ); + /// } else if (_controller.view == CalendarView.month) { + /// final Appointment appointment = + /// calendarAppointmentDetails.appointments.first; + /// return Container( + /// decoration: BoxDecoration( + /// color: appointment.color, + /// shape: BoxShape.rectangle, + /// borderRadius: BorderRadius.all(Radius.circular(4.0)), + /// gradient: LinearGradient( + /// colors: [Colors.red, Colors.cyan], + /// begin: Alignment.centerRight, + /// end: Alignment.centerLeft)), + /// alignment: Alignment.center, + /// child: appointment.isAllDay + /// ? Text('${appointment.subject}', + /// textAlign: TextAlign.left, + /// style: TextStyle( + /// color: Colors.white, fontSize: 10)) + /// : Column( + /// mainAxisAlignment: MainAxisAlignment.center, + /// children: [ + /// Text('${appointment.subject}', + /// textAlign: TextAlign.left, + /// style: TextStyle( + /// color: Colors.white, fontSize: 10)), + /// Text( + /// '${DateFormat('hh:mm a'). + /// format(appointment.startTime)} - ' + + /// '${DateFormat('hh:mm a'). + /// format(appointment.endTime)}', + /// textAlign: TextAlign.left, + /// style: TextStyle( + /// color: Colors.white, fontSize: 10)) + /// ], + /// )); + /// } else { + /// final Appointment appointment = + /// calendarAppointmentDetails.appointments.first; + /// return Container( + /// width: calendarAppointmentDetails.bounds.width, + /// height: calendarAppointmentDetails.bounds.height, + /// child: Text(appointment.subject), + /// ); + /// } + /// }, + /// ), + /// ), + /// ); + /// } + /// ``` + final CalendarAppointmentBuilder appointmentBuilder; + + /// A builder that builds a widget that replaces the time region view in day, + /// week, workweek, and timeline day, week, workweek views. + /// + /// ```dart + /// + /// List _getTimeRegions() { + /// final List regions = []; + /// DateTime date = DateTime.now(); + /// date = DateTime(date.year, date.month, date.day, 12, 0, 0); + /// regions.add(TimeRegion( + /// startTime: date, + /// endTime: date.add(Duration(hours: 2)), + /// enablePointerInteraction: false, + /// color: Colors.grey.withOpacity(0.2), + /// text: 'Break')); + /// + /// return regions; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfCalendar( + /// view: CalendarView.day, + /// specialRegions: _getTimeRegions(), + /// timeRegionBuilder: + /// (BuildContext context, TimeRegionDetails timeRegionDetails) { + /// return Container( + /// margin: EdgeInsets.all(1), + /// alignment: Alignment.center, + /// child: Text( + /// timeRegionDetails.region.text, + /// style: TextStyle(color: Colors.black), + /// ), + /// decoration: BoxDecoration( + /// shape: BoxShape.rectangle, + /// borderRadius: BorderRadius.all(Radius.circular(4.0)), + /// gradient: LinearGradient( + /// colors: [timeRegionDetails.region.color, Colors.cyan], + /// begin: Alignment.centerRight, + /// end: Alignment.centerLeft)), + /// ); + /// }, + /// ), + /// )); + /// } + /// ``` + final TimeRegionBuilder timeRegionBuilder; + /// A builder that builds a widget, replace the schedule month header /// widget in calendar schedule view. /// @@ -580,6 +719,27 @@ class SfCalendar extends StatefulWidget { /// ``` final double headerHeight; + /// Adds padding at the right end of a cell to interact when the calendar + /// cells have appointments. + /// + /// defaults to '1'. + /// + /// Note: This is not applicable for month agenda and schedule view + /// appointments. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCalendar( + /// view: CalendarView.day, + /// cellEndPadding: 5, + /// ), + /// ); + /// } + /// + /// ``` + final double cellEndPadding; + /// The text style for the text in the [Appointment] view in [SfCalendar]. /// /// Defaults to null. @@ -1336,7 +1496,7 @@ class SfCalendar extends StatefulWidget { ///recurrenceProperties.recurrenceRange = RecurrenceRange.count; ///recurrenceProperties.interval = 2; ///recurrenceProperties.recurrenceCount = 10; - // + /// ///Appointment appointment = Appointment( /// startTime: DateTime(2019, 12, 16, 10), /// endTime: DateTime(2019, 12, 16, 12), @@ -1372,7 +1532,7 @@ class _SfCalendarState extends State SfLocalizations _localizations; double _minWidth, _minHeight, _textScaleFactor; SfCalendarThemeData _calendarTheme; - ValueNotifier _headerHoverNotifier; + ValueNotifier _headerHoverNotifier, _resourceHoverNotifier; ValueNotifier<_ScheduleViewHoveringDetails> _agendaDateNotifier, _agendaViewNotifier; @@ -1428,6 +1588,25 @@ class _SfCalendarState extends State double _calendarViewWidth; ValueNotifier _viewChangeNotifier; + /// Used for hold the schedule display date value used for show nothing + /// planned text on schedule view. + DateTime _scheduleDisplayDate; + + /// Used to check the current platform as mobile platform(android or iOS) + bool _isMobilePlatform; + + /// Used to check the desktop platform needs mobile platform UI. + bool _useMobilePlatformUI; + + /// Fade animation controller to controls fade animation + AnimationController _fadeInController; + + /// Fade animation animated on view changing and web view navigation. + Animation _fadeIn; + + /// Opacity of widget handles by fade animation. + double _opacity; + @override void initState() { _timeZoneLoaded = false; @@ -1440,6 +1619,8 @@ class _SfCalendarState extends State _resourceImageNotifier = ValueNotifier(false); _headerHoverNotifier = ValueNotifier(null) ..addListener(_updateViewHeaderHover); + _resourceHoverNotifier = ValueNotifier(null) + ..addListener(_updateViewHeaderHover); _controller = widget.controller ?? CalendarController(); _controller.selectedDate = widget.initialSelectedDate; _selectedDate = widget.initialSelectedDate; @@ -1448,6 +1629,7 @@ class _SfCalendarState extends State _currentDate = getValidDate(widget.minDate, widget.maxDate, widget.initialDisplayDate); _controller.displayDate = _currentDate; + _scheduleDisplayDate = _controller.displayDate; _controller._view = widget.view; _view = _controller.view; _updateCurrentVisibleDates(); @@ -1473,6 +1655,9 @@ class _SfCalendarState extends State _blackoutDates = _cloneList(widget.blackoutDates); _viewChangeNotifier = ValueNotifier(false) ..addListener(_updateViewChangePopup); + + _opacity = 1; + super.initState(); } @@ -1513,6 +1698,8 @@ class _SfCalendarState extends State if (widget.controller != null) { _controller.selectedDate = widget.controller.selectedDate; _controller.displayDate = widget.controller.displayDate ?? _currentDate; + _scheduleDisplayDate = + widget.controller.displayDate ?? _scheduleDisplayDate; _controller.view = widget.controller.view ?? _view; } else { _controller.selectedDate = widget.initialSelectedDate; @@ -1573,6 +1760,7 @@ class _SfCalendarState extends State widget.minDate, widget.maxDate, widget.initialDisplayDate); _controller.displayDate = _currentDate; + _scheduleDisplayDate = _controller.displayDate; } if (!_isDateCollectionEqual(widget.blackoutDates, _blackoutDates)) { @@ -1666,11 +1854,31 @@ class _SfCalendarState extends State ? _minHeight : constraints.maxHeight; + _isMobilePlatform = _isMobileLayout(Theme.of(context).platform); + _useMobilePlatformUI = _isMobileLayoutUI(_minWidth, _isMobilePlatform); + + _fadeInController ??= AnimationController( + duration: Duration(milliseconds: _isMobilePlatform ? 500 : 600), + vsync: this) + ..addListener(() { + setState(() { + _opacity = _fadeIn.value; + }); + }); + _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 && - _isScheduleWebUI(_minWidth) != _isScheduleWebUI(_actualWidth) && + _useMobilePlatformUI != + _isMobileLayoutUI(_actualWidth, _isMobilePlatform) && _nextDates.isNotEmpty) { _agendaScrollController?.removeListener(_handleScheduleViewScrolled); _initScheduleViewProperties(); @@ -1682,7 +1890,7 @@ class _SfCalendarState extends State _agendaDateViewWidth = _minWidth * 0.15; /// Restrict the maximum agenda date view width to 60 on web view. - if (_agendaDateViewWidth > 60 && kIsWeb) { + if (_agendaDateViewWidth > 60 && !_isMobilePlatform) { _agendaDateViewWidth = 60; } @@ -1701,10 +1909,7 @@ class _SfCalendarState extends State ? addAgenda(height, _isRTL) : _addChildren(agendaHeight, height, _minWidth, _isRTL), ), - onTapUp: (TapUpDetails details) { - _removeDatePicker(); - }, - onLongPressStart: (LongPressStartDetails details) { + onTap: () { _removeDatePicker(); }, ); @@ -1727,6 +1932,10 @@ class _SfCalendarState extends State _agendaDateNotifier.removeListener(_agendaSelectedDateListener); } + if (_resourceHoverNotifier != null) { + _resourceHoverNotifier.removeListener(_updateViewHeaderHover); + } + if (widget.dataSource != null) { widget.dataSource.removeListener(_dataSourceChangedListener); } @@ -1771,13 +1980,26 @@ class _SfCalendarState extends State viewEndDate = _getMonthEndDate(currentMonthDate); } - // ignore: await_only_futures - _visibleAppointments = await _getVisibleAppointments( - viewStartDate, - viewEndDate, - _appointments, - widget.timeZone, - _view == CalendarView.month || _isTimelineView(_view)); + final List tempVisibleAppointment = + // ignore: await_only_futures + await _getVisibleAppointments( + viewStartDate, + viewEndDate, + _appointments, + widget.timeZone, + _view == CalendarView.month || _isTimelineView(_view)); + if (_isCollectionEqual(_visibleAppointments, tempVisibleAppointment)) { + if (mounted) { + setState(() { + // Updates the calendar widget because it trigger to change the + // header view text. + }); + } + + return; + } + + _visibleAppointments = tempVisibleAppointment; /// Update all day appointment related implementation in calendar, /// because time label view needs the top position. @@ -1851,9 +2073,9 @@ class _SfCalendarState extends State } _currentDate = date; - _controller.displayDate = date; if (date.month != _headerUpdateNotifier.value.month || date.year != _headerUpdateNotifier.value.year) { + _controller.displayDate = date; _headerUpdateNotifier.value = date; } @@ -1888,9 +2110,9 @@ class _SfCalendarState extends State } _currentDate = date; - _controller.displayDate = date; if (date.month != _headerUpdateNotifier.value.month || date.year != _headerUpdateNotifier.value.year) { + _controller.displayDate = date; _headerUpdateNotifier.value = date; } @@ -1905,8 +2127,7 @@ class _SfCalendarState extends State void _calendarValueChangedListener(String property) { _removeDatePicker(); if (property == 'selectedDate') { - if (isSameDate(_selectedDate, _controller.selectedDate) && - _isSameTimeSlot(_selectedDate, _controller.selectedDate)) { + if (_isSameTimeSlot(_selectedDate, _controller.selectedDate)) { return; } @@ -1914,24 +2135,7 @@ class _SfCalendarState extends State _selectedDate = _controller.selectedDate; }); } else if (property == 'displayDate') { - if (isSameDate(_currentDate, _controller.displayDate) || - (_view != CalendarView.schedule && - isDateWithInDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[_currentViewVisibleDates.length - 1], - _controller.displayDate))) { - _currentDate = _controller.displayDate; - return; - } - - setState(() { - _currentDate = _controller.displayDate; - _updateCurrentVisibleDates(); - if (_view == CalendarView.schedule) { - _agendaScrollController?.removeListener(_handleScheduleViewScrolled); - _initScheduleViewProperties(); - } - }); + _updateDisplayDate(); } else if (property == 'calendarView') { if (_view == _controller.view) { return; @@ -1946,7 +2150,11 @@ class _SfCalendarState extends State _controller.displayDate = _currentDate; } + _fadeInController.reset(); + _fadeInController.forward(); + _agendaScrollController = ScrollController(initialScrollOffset: 0); if (_view == CalendarView.schedule) { + _scheduleDisplayDate = _controller.displayDate; if (_shouldRaiseViewChangedCallback(widget.onViewChanged)) { _raiseViewChangedCallback(widget, visibleDates: []..add(_controller.displayDate)); @@ -1959,6 +2167,81 @@ class _SfCalendarState extends State } } + void _updateDisplayDate() { + switch (_view) { + case CalendarView.schedule: + { + if (isSameDate(_currentDate, _controller.displayDate)) { + _currentDate = _controller.displayDate; + return; + } + + _fadeInController.reset(); + _fadeInController.forward(); + setState(() { + _currentDate = _controller.displayDate; + _scheduleDisplayDate = _controller.displayDate; + _updateCurrentVisibleDates(); + _agendaScrollController + ?.removeListener(_handleScheduleViewScrolled); + _initScheduleViewProperties(); + }); + break; + } + case CalendarView.month: + { + if (isSameDate(_currentDate, _controller.displayDate) || + (isDateWithInDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[ + _currentViewVisibleDates.length - 1], + _controller.displayDate) && + (widget.monthViewSettings.numberOfWeeksInView != 6 || + (widget.monthViewSettings.numberOfWeeksInView == 6 && + _controller.displayDate.month == + _currentViewVisibleDates[ + _currentViewVisibleDates.length ~/ 2] + .month)))) { + _currentDate = _controller.displayDate; + return; + } + + _fadeInController.reset(); + _fadeInController.forward(); + setState(() { + _currentDate = _controller.displayDate; + _updateCurrentVisibleDates(); + }); + break; + } + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + case CalendarView.timelineMonth: + { + if (isSameDate(_currentDate, _controller.displayDate) || + isDateWithInDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - 1], + _controller.displayDate)) { + _currentDate = _controller.displayDate; + return; + } + + _fadeInController.reset(); + _fadeInController.forward(); + setState(() { + _currentDate = _controller.displayDate; + _updateCurrentVisibleDates(); + }); + break; + } + } + } + void _updateCurrentVisibleDates() { final List nonWorkingDays = (_view == CalendarView.workWeek || _view == CalendarView.timelineWorkWeek) @@ -2079,6 +2362,18 @@ class _SfCalendarState extends State /// the collection modified based on the data source's add and remove action. void _updateVisibleAppointmentCollection( List visibleAppointmentCollection) { + if (_view == CalendarView.schedule) { + setState(() { + /// Update the view when the appointment collection changed. + }); + return; + } + + if (_isCollectionEqual( + _visibleAppointments, visibleAppointmentCollection)) { + return; + } + setState(() { _visibleAppointments = visibleAppointmentCollection; @@ -2165,26 +2460,6 @@ class _SfCalendarState extends State } } - _AppointmentView _getAppointmentView(Appointment appointment) { - _AppointmentView appointmentRenderer; - for (int i = 0; i < _allDayAppointmentViewCollection.length; i++) { - final _AppointmentView view = _allDayAppointmentViewCollection[i]; - if (view.appointment == null) { - appointmentRenderer = view; - break; - } - } - - if (appointmentRenderer == null) { - appointmentRenderer = _AppointmentView(); - _allDayAppointmentViewCollection.add(appointmentRenderer); - } - - appointmentRenderer.appointment = appointment; - appointmentRenderer.canReuse = false; - return appointmentRenderer; - } - void _updateAppointmentView(List allDayAppointments) { for (int i = 0; i < allDayAppointments.length; i++) { _AppointmentView appointmentView; @@ -2245,14 +2520,20 @@ class _SfCalendarState extends State if (intersectingAppointments.isNotEmpty) { final int maxPosition = intersectingAppointments - .reduce((_AppointmentView currentAppView, - _AppointmentView nextAppView) => - currentAppView.position > nextAppView.position - ? currentAppView - : nextAppView) - .position; + .reduce((_AppointmentView currentAppView, + _AppointmentView nextAppView) => + currentAppView.position > nextAppView.position + ? currentAppView + : nextAppView) + .position + + 1; + for (int j = 0; j < intersectingAppointments.length; j++) { - intersectingAppointments[j].maxPositions = maxPosition + 1; + final _AppointmentView appointmentView = intersectingAppointments[j]; + if (appointmentView.maxPositions != -1) { + continue; + } + appointmentView.maxPositions = maxPosition; } } } @@ -2317,8 +2598,10 @@ class _SfCalendarState extends State _allDayAppointmentViewCollection .sort((_AppointmentView app1, _AppointmentView app2) { if (app1.appointment != null && app2.appointment != null) { - return (app2.endIndex - app2.startIndex) > - (app1.endIndex - app1.startIndex) + return (app2.appointment.endTime + .difference(app2.appointment.startTime)) > + (app1.appointment.endTime + .difference(app1.appointment.startTime)) ? 1 : 0; } @@ -2344,9 +2627,13 @@ class _SfCalendarState extends State //// Calculate the appointment view position and max position. _updateAppointmentPositionAndMaxPosition(allDayAppointmentView); + _updateAllDayPanelHeight(); + } + void _updateAllDayPanelHeight() { int maxPosition = 0; - if (_allDayAppointmentViewCollection.isNotEmpty) { + if (_allDayAppointmentViewCollection != null && + _allDayAppointmentViewCollection.isNotEmpty) { maxPosition = _allDayAppointmentViewCollection .reduce( (_AppointmentView currentAppView, _AppointmentView nextAppView) => @@ -2379,27 +2666,69 @@ class _SfCalendarState extends State _agendaViewNotifier.value = null; } + if (_resourceHoverNotifier.value != null) { + _resourceHoverNotifier.value = null; + } + _headerHoverNotifier.value = Offset(localPosition.dx, localPosition.dy); } - void _updateMouseHoverPosition(Offset globalPosition, + void _updateMouseHoverPosition( + Offset globalPosition, bool isScheduleDisplayDate, [bool isRTL, DateTime currentDate, double startPosition, - double padding = 0]) { + double padding = 0, + bool isResourceEnabled = false]) { + if (_isMobilePlatform) { + return; + } + final RenderBox box = context.findRenderObject(); final Offset localPosition = box.globalToLocal(globalPosition); if (localPosition.dy < widget.headerHeight) { _updateMouseHoveringForHeader(localPosition); } else { + if (isResourceEnabled && + ((isRTL && + localPosition.dx > + (_minWidth - widget.resourceViewSettings.size)) || + (!isRTL && + localPosition.dx < widget.resourceViewSettings.size)) && + localPosition.dy > startPosition && + (_shouldRaiseCalendarTapCallback(widget.onTap) || + _shouldRaiseCalendarLongPressCallback(widget.onLongPress))) { + if (_headerHoverNotifier.value != null) { + _headerHoverNotifier.value = null; + } + + if (_agendaViewNotifier.value != null) { + _agendaViewNotifier.value = null; + } + + if (_agendaDateNotifier.value != null) { + _agendaDateNotifier.value = null; + } + + if (_resourceHoverNotifier.value != null) { + _resourceHoverNotifier.value = null; + } + + final double yPosition = + (_resourcePanelScrollController.offset + localPosition.dy) - + startPosition; + + _resourceHoverNotifier.value = Offset(localPosition.dx, yPosition); + } + if (_view != CalendarView.month && _view != CalendarView.schedule) { return; } double yPosition = localPosition.dy; double xPosition = localPosition.dx; - double dateViewWidth = - _getAgendaViewDayLabelWidth(widget.scheduleViewSettings, _minWidth); + double dateViewWidth = _getAgendaViewDayLabelWidth( + widget.scheduleViewSettings, _useMobilePlatformUI); if (_view == CalendarView.month) { currentDate = _selectedDate; final double agendaHeight = _getMonthAgendaHeight(); @@ -2409,7 +2738,7 @@ class _SfCalendarState extends State dateViewWidth = 0; } - if (dateViewWidth > 60 && kIsWeb) { + if (dateViewWidth > 60 && !_isMobilePlatform) { dateViewWidth = 60; } } else { @@ -2428,6 +2757,10 @@ class _SfCalendarState extends State _agendaViewNotifier.value = null; } + if (_resourceHoverNotifier.value != null) { + _resourceHoverNotifier.value = null; + } + if (widget.onTap == null && widget.onLongPress == null && !widget.allowViewNavigation) { @@ -2444,6 +2777,10 @@ class _SfCalendarState extends State /// eg., if the agenda view holds one all day appointment in /// schedule view then it have top padding because the agenda view /// minimum panel height as appointment height specified in setting. + if (_view == CalendarView.month) { + yPosition += _agendaScrollController?.offset; + xPosition -= isRTL ? 0 : dateViewWidth; + } yPosition -= padding; if (_headerHoverNotifier.value != null) { _headerHoverNotifier.value = null; @@ -2453,34 +2790,46 @@ class _SfCalendarState extends State _agendaDateNotifier.value = null; } + if (_resourceHoverNotifier.value != null) { + _resourceHoverNotifier.value = null; + } + if (isScheduleDisplayDate && + widget.onTap == null && + widget.onLongPress == null) { + _agendaViewNotifier.value = null; + return; + } _agendaViewNotifier.value = _ScheduleViewHoveringDetails( currentDate, Offset(xPosition, yPosition)); } } } - void _pointerEnterEvent(PointerEnterEvent event, + void _pointerEnterEvent(PointerEnterEvent event, bool isScheduleDisplayDate, [bool isRTL, DateTime currentDate, double startPosition, - double padding = 0]) { - _updateMouseHoverPosition( - event.position, isRTL, currentDate, startPosition, padding); + double padding = 0, + bool resourceEnabled]) { + _updateMouseHoverPosition(event.position, isScheduleDisplayDate, isRTL, + currentDate, startPosition, padding, resourceEnabled ?? false); } - void _pointerHoverEvent(PointerHoverEvent event, + void _pointerHoverEvent(PointerHoverEvent event, bool isScheduleDisplayDate, [bool isRTL, DateTime currentDate, double startPosition, - double padding = 0]) { - _updateMouseHoverPosition( - event.position, isRTL, currentDate, startPosition, padding); + double padding = 0, + bool resourceEnabled]) { + _updateMouseHoverPosition(event.position, isScheduleDisplayDate, isRTL, + currentDate, startPosition, padding, resourceEnabled ?? false); } void _pointerExitEvent(PointerExitEvent event) { _headerHoverNotifier.value = null; _agendaDateNotifier.value = null; _agendaViewNotifier.value = null; + _resourceHoverNotifier.value = null; } /// Return the all day appointment count from appointment collection. @@ -2533,8 +2882,8 @@ class _SfCalendarState extends State /// display date and today date. /// schedule current date always hold the maximum date compared to /// display date and today date - DateTime scheduleDisplayDate = - getValidDate(widget.minDate, widget.maxDate, _controller.displayDate); + DateTime scheduleDisplayDate = _scheduleDisplayDate; + //getValidDate(widget.minDate, widget.maxDate, _controller.displayDate); DateTime scheduleCurrentDate = DateTime.now(); if (scheduleDisplayDate.isAfter(scheduleCurrentDate)) { final DateTime tempDate = scheduleDisplayDate; @@ -2552,7 +2901,7 @@ class _SfCalendarState extends State widget.minDate, scheduleDisplayDate, widget.scheduleViewSettings, - _minWidth); + _useMobilePlatformUI); /// Assign minimum date value to schedule display date when the minimum /// date is after of schedule display date @@ -2573,7 +2922,7 @@ class _SfCalendarState extends State widget.maxDate, scheduleCurrentDate, widget.scheduleViewSettings, - _minWidth); + _useMobilePlatformUI); /// Assign maximum date value to schedule current date when the maximum /// date is before of schedule current date @@ -2581,9 +2930,9 @@ class _SfCalendarState extends State _maxDate.isBefore(scheduleCurrentDate) ? scheduleCurrentDate : _maxDate; _maxDate = _maxDate.isAfter(widget.maxDate) ? widget.maxDate : _maxDate; - final bool isWebView = kIsWeb && _minWidth > _kMobileViewWidth; final bool hideEmptyAgendaDays = - widget.scheduleViewSettings.hideEmptyScheduleWeek || isWebView; + widget.scheduleViewSettings.hideEmptyScheduleWeek || + !_useMobilePlatformUI; if (index > 0) { /// Add next 100 dates to next dates collection when index @@ -2752,25 +3101,26 @@ class _SfCalendarState extends State } /// calculate the day label(eg., May 25) width based on schedule setting. - final double viewPadding = - _getAgendaViewDayLabelWidth(widget.scheduleViewSettings, _minWidth); + final double viewPadding = _getAgendaViewDayLabelWidth( + widget.scheduleViewSettings, _useMobilePlatformUI); - final double viewTopPadding = !isWebView ? padding : 0; + final double viewTopPadding = _useMobilePlatformUI ? padding : 0; /// calculate the total height using height variable /// web view does not have week label. - double height = - isWebView ? 0 : widget.scheduleViewSettings.weekHeaderSettings.height; + 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. - final bool isNeedMonthBuilder = isWebView - ? false - : prevEndDate.month != startDate.month || - prevEndDate.year != startDate.year; + final bool isNeedMonthBuilder = _useMobilePlatformUI + ? prevEndDate.month != startDate.month || + prevEndDate.year != startDate.year + : false; /// Web view does not have month label. height += isNeedMonthBuilder @@ -2782,7 +3132,7 @@ class _SfCalendarState extends State _getScheduleAllDayAppointmentHeight(null, widget.scheduleViewSettings); /// Calculate the divider height and color when it is web view. - final double dividerHeight = isWebView ? 1 : 0; + final double dividerHeight = _useMobilePlatformUI ? 0 : 1; Color dividerColor = widget.cellBorderColor ?? _calendarTheme.cellBorderColor; dividerColor = dividerColor.withOpacity(dividerColor.opacity * 0.5); @@ -2798,7 +3148,7 @@ class _SfCalendarState extends State int allDayEventCount = 0; /// Web view does not differentiate all day and normal appointment. - if (!isWebView) { + if (_useMobilePlatformUI) { allDayEventCount = _getAllDayCount(_currentDateAppointment); } @@ -2822,7 +3172,7 @@ class _SfCalendarState extends State (numberOfEvents + dateAppointmentKeys.length) * padding; /// Add appointment height and week view end padding to height. - height += appointmentHeight + (isWebView ? 0 : padding); + height += appointmentHeight + (_useMobilePlatformUI ? padding : 0); /// Create the generated view details to store the view height /// and its intersection point. @@ -2845,20 +3195,18 @@ class _SfCalendarState extends State final List widgets = []; /// Web view does not have month label. - if (!isWebView) { + if (_useMobilePlatformUI) { if (isNeedMonthBuilder) { /// Add the height of month label to total height of view. topHeight += widget.scheduleViewSettings.monthHeaderSettings.height; - widgets.add( - _getMonthOrWeekHeader(startDate, endDate, isRTL, true, isWebView)); + widgets.add(_getMonthOrWeekHeader(startDate, endDate, isRTL, true)); /// Add the week label padding value to top position and total height. topHeight += viewTopPadding; height += viewTopPadding; } - widgets.add(_getMonthOrWeekHeader( - startDate, endDate, isRTL, false, isWebView, + 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. @@ -2868,7 +3216,7 @@ class _SfCalendarState extends State /// Calculate the day label(May, 25) height based on appointment height and /// assign the label maximum height as 60. double appointmentViewHeaderHeight = appointmentViewHeight + (2 * padding); - if (!isWebView) { + if (_useMobilePlatformUI) { appointmentViewHeaderHeight = appointmentViewHeaderHeight > 60 ? 60 : appointmentViewHeaderHeight; } @@ -2876,10 +3224,10 @@ class _SfCalendarState extends State /// Check the week date needs month header at in between the appointment /// views. - bool isNeedInBetweenMonthBuilder = isWebView - ? false - : startDate.month != - (isSameOrBeforeDate(_maxDate, endDate) ? endDate : _maxDate).month; + bool isNeedInBetweenMonthBuilder = _useMobilePlatformUI + ? startDate.month != + (isSameOrBeforeDate(_maxDate, endDate) ? endDate : _maxDate).month + : false; /// Check the end date month have appointments or not. bool isNextMonthHasNoAppointment = false; @@ -2930,16 +3278,16 @@ class _SfCalendarState extends State /// Add appointment height to height when the view have display date view. if (isNeedDisplayDateHighlight) { - height += isWebView - ? appointmentViewHeaderHeight + dividerHeight - : appointmentViewHeaderHeight; + height += _useMobilePlatformUI + ? appointmentViewHeaderHeight + : appointmentViewHeaderHeight + dividerHeight; } /// Add appointment height to height when the view have current date view. if (isNeedCurrentDateHighlight) { - height += isWebView - ? appointmentViewHeaderHeight + dividerHeight - : appointmentViewHeaderHeight; + height += _useMobilePlatformUI + ? appointmentViewHeaderHeight + : appointmentViewHeaderHeight + dividerHeight; } /// display date highlight added boolean variable used to identify the @@ -2959,7 +3307,7 @@ class _SfCalendarState extends State int allDayEventCount = 0; /// Web view does not differentiate all day and normal appointment. - if (!isWebView) { + if (_useMobilePlatformUI) { allDayEventCount = _getAllDayCount(currentAppointments); } @@ -2975,12 +3323,11 @@ class _SfCalendarState extends State : previousHeight + height - interSectPoint - viewTopPadding; /// Web view does not have month label; - if (!isWebView) { + if (_useMobilePlatformUI) { interSectPoint += widget.scheduleViewSettings.monthHeaderSettings.height + viewTopPadding; - widgets.add(_getMonthOrWeekHeader( - currentDate, null, isRTL, true, isWebView, + widgets.add(_getMonthOrWeekHeader(currentDate, null, isRTL, true, isNeedTopPadding: true)); } } @@ -2998,11 +3345,10 @@ class _SfCalendarState extends State highlightViewStartPosition, viewPadding, appointmentViewHeaderHeight, - padding, - isWebView)); + padding)); /// Add divider at end of each of the week days in web view. - if (isWebView) { + if (!_useMobilePlatformUI) { widgets.add(Divider( height: dividerHeight, thickness: 1, @@ -3030,11 +3376,10 @@ class _SfCalendarState extends State highlightViewStartPosition, viewPadding, appointmentViewHeaderHeight, - padding, - isWebView)); + padding)); /// Add divider at end of each of the week days in web view. - if (isWebView) { + if (!_useMobilePlatformUI) { widgets.add(Divider( height: dividerHeight, thickness: 1, @@ -3065,17 +3410,23 @@ class _SfCalendarState extends State : -(previousHeight + height - interSectPoint); interSectPoint += appointmentViewPadding; + currentAppointments.sort((Appointment app1, Appointment app2) => + app1._actualStartTime.compareTo(app2._actualStartTime)); + currentAppointments.sort((Appointment app1, Appointment app2) => + _orderAppointmentsAscending(app1.isAllDay, app2.isAllDay)); + currentAppointments.sort((Appointment app1, Appointment app2) => + _orderAppointmentsAscending(app1._isSpanned, app2._isSpanned)); /// Add appointment view to the current views collection. widgets.add(MouseRegion( onEnter: (PointerEnterEvent event) { - _pointerEnterEvent(event, isRTL, currentDate, viewStartPosition, - appointmentViewTopPadding); + _pointerEnterEvent(event, false, isRTL, currentDate, + viewStartPosition, appointmentViewTopPadding); }, onExit: _pointerExitEvent, onHover: (PointerHoverEvent event) { - _pointerHoverEvent(event, isRTL, currentDate, viewStartPosition, - appointmentViewTopPadding); + _pointerHoverEvent(event, false, isRTL, currentDate, + viewStartPosition, appointmentViewTopPadding); }, child: GestureDetector( child: _ScheduleAppointmentView( @@ -3093,7 +3444,8 @@ class _SfCalendarState extends State _agendaDateNotifier, _minWidth, isRTL, - _textScaleFactor), + _textScaleFactor, + _isMobilePlatform), size: Size(viewPadding, appointmentViewHeaderHeight))), content: Container( padding: EdgeInsets.fromLTRB( @@ -3101,21 +3453,23 @@ class _SfCalendarState extends State appointmentViewTopPadding, isRTL ? viewPadding : 0, appointmentViewTopPadding), - child: CustomPaint( - painter: _AgendaViewPainter( - null, - widget.scheduleViewSettings, - currentDate, - currentAppointments, - isRTL, - _locale, - _localizations, - _calendarTheme, - _agendaViewNotifier, - widget.appointmentTimeTextFormat, - viewPadding, - _textScaleFactor), - size: Size(_minWidth - viewPadding, panelHeight)), + child: _AgendaViewLayout( + null, + widget.scheduleViewSettings, + currentDate, + currentAppointments, + isRTL, + _locale, + _localizations, + _calendarTheme, + _agendaViewNotifier, + widget.appointmentTimeTextFormat, + viewPadding, + _textScaleFactor, + _isMobilePlatform, + widget.appointmentBuilder, + _minWidth - viewPadding, + panelHeight), )), onTapUp: (TapUpDetails details) { _removeDatePicker(); @@ -3133,7 +3487,7 @@ class _SfCalendarState extends State } _raiseCallbackForScheduleView(currentDate, details.localPosition, - currentAppointments, viewPadding, padding, true, isWebView); + currentAppointments, viewPadding, padding, true); }, onLongPressStart: (LongPressStartDetails details) { _removeDatePicker(); @@ -3151,14 +3505,14 @@ class _SfCalendarState extends State } _raiseCallbackForScheduleView(currentDate, details.localPosition, - currentAppointments, viewPadding, padding, false, isWebView); + currentAppointments, viewPadding, padding, false); }, ))); interSectPoint += panelHeight + dividerHeight; /// Add divider at end of each of the week days in web view. - if (isWebView) { + if (!_useMobilePlatformUI) { widgets.add(Divider( height: dividerHeight, thickness: 1, @@ -3180,11 +3534,10 @@ class _SfCalendarState extends State highlightViewStartPosition, viewPadding, appointmentViewHeaderHeight, - padding, - isWebView)); + padding)); /// Add divider at end of each of the week days in web view. - if (isWebView) { + if (!_useMobilePlatformUI) { widgets.add(Divider( height: dividerHeight, thickness: 1, @@ -3212,11 +3565,10 @@ class _SfCalendarState extends State highlightViewStartPosition, viewPadding, appointmentViewHeaderHeight, - padding, - isWebView)); + padding)); /// Add divider at end of each of the week days in web view. - if (isWebView) { + if (!_useMobilePlatformUI) { widgets.add(Divider( height: dividerHeight, thickness: 1, @@ -3235,7 +3587,7 @@ class _SfCalendarState extends State /// Add Month label at end of the view when the week start and end date /// month different and week does not have appointments or week have /// appointments but end date month dates does not have an appointment - if (!isWebView && + if (_useMobilePlatformUI && isNeedInBetweenMonthBuilder && isNextMonthHasNoAppointment && isSameOrBeforeDate(_maxDate, endDate)) { @@ -3252,8 +3604,7 @@ class _SfCalendarState extends State viewTopPadding; topHeight += widget.scheduleViewSettings.monthHeaderSettings.height + viewTopPadding; - widgets.add(_getMonthOrWeekHeader( - endDate, endDate, isRTL, true, isWebView, + widgets.add(_getMonthOrWeekHeader(endDate, endDate, isRTL, true, isNeedTopPadding: true)); } @@ -3269,11 +3620,10 @@ class _SfCalendarState extends State highlightViewStartPosition, viewPadding, appointmentViewHeaderHeight, - padding, - isWebView)); + padding)); /// Add divider at end of each of the week days in web view. - if (isWebView) { + if (!_useMobilePlatformUI) { widgets.add(Divider( height: dividerHeight, thickness: 1, @@ -3295,11 +3645,10 @@ class _SfCalendarState extends State highlightViewStartPosition, viewPadding, appointmentViewHeaderHeight, - padding, - isWebView)); + padding)); /// Add divider at end of each of the week days in web view. - if (isWebView) { + if (!_useMobilePlatformUI) { widgets.add(Divider( height: dividerHeight, thickness: 1, @@ -3321,8 +3670,8 @@ class _SfCalendarState extends State return Container(height: height, child: Column(children: widgets)); } - Widget _getMonthOrWeekHeader(DateTime startDate, DateTime endDate, bool isRTL, - bool isMonthLabel, bool isWebView, + Widget _getMonthOrWeekHeader( + DateTime startDate, DateTime endDate, bool isRTL, bool isMonthLabel, {double viewPadding = 0, bool isNeedTopPadding = false}) { const double padding = 5; Widget headerWidget; @@ -3357,7 +3706,7 @@ class _SfCalendarState extends State isMonthLabel, isRTL, _locale, - isWebView, + _useMobilePlatformUI, _agendaViewNotifier, _calendarTheme, _localizations, @@ -3406,17 +3755,16 @@ class _SfCalendarState extends State double highlightViewStartPosition, double viewHeaderWidth, double displayDateHighlightHeight, - double padding, - bool isWebView) { + double padding) { return MouseRegion( onEnter: (PointerEnterEvent event) { - _pointerEnterEvent( - event, isRTL, currentDisplayDate, highlightViewStartPosition); + _pointerEnterEvent(event, true, isRTL, currentDisplayDate, + highlightViewStartPosition); }, onExit: _pointerExitEvent, onHover: (PointerHoverEvent event) { - _pointerHoverEvent( - event, isRTL, currentDisplayDate, highlightViewStartPosition); + _pointerHoverEvent(event, true, isRTL, currentDisplayDate, + highlightViewStartPosition); }, child: GestureDetector( child: _ScheduleAppointmentView( @@ -3434,7 +3782,8 @@ class _SfCalendarState extends State _agendaDateNotifier, _minWidth, isRTL, - _textScaleFactor), + _textScaleFactor, + _isMobilePlatform), size: Size(viewHeaderWidth, displayDateHighlightHeight))), content: Container( padding: EdgeInsets.fromLTRB(isRTL ? 0 : viewHeaderWidth, 0, @@ -3447,7 +3796,7 @@ class _SfCalendarState extends State false, isRTL, _locale, - isWebView, + _useMobilePlatformUI, _agendaViewNotifier, _calendarTheme, _localizations, @@ -3478,7 +3827,6 @@ class _SfCalendarState extends State viewHeaderWidth, padding, true, - isWebView, isDisplayDate: true); }, onLongPressStart: (LongPressStartDetails details) { @@ -3503,7 +3851,6 @@ class _SfCalendarState extends State viewHeaderWidth, padding, false, - isWebView, isDisplayDate: true); }, )); @@ -3516,7 +3863,6 @@ class _SfCalendarState extends State double viewHeaderWidth, double padding, bool isTapCallback, - bool isWebView, {bool isDisplayDate = false}) { /// Check the touch position on day label if ((!_isRTL && viewHeaderWidth >= offset.dx) || @@ -3574,7 +3920,7 @@ class _SfCalendarState extends State for (int k = 0; k < appointments.length; k++) { final Appointment appointment = appointments[k]; final double currentAppointmentHeight = - (!isWebView && _isAllDayAppointmentView(appointment) + (_useMobilePlatformUI && _isAllDayAppointmentView(appointment) ? allDayItemHeight : itemHeight) + padding; @@ -3610,9 +3956,9 @@ class _SfCalendarState extends State } Widget addAgenda(double height, bool isRTL) { - final bool isWebView = kIsWeb && _minWidth > _kMobileViewWidth; final bool hideEmptyAgendaDays = - widget.scheduleViewSettings.hideEmptyScheduleWeek || isWebView; + widget.scheduleViewSettings.hideEmptyScheduleWeek || + !_useMobilePlatformUI; /// return empty view when [hideEmptyAgendaDays] enabled and /// the appointments as empty. @@ -3636,8 +3982,13 @@ class _SfCalendarState extends State /// It return min date user assigned when the [hideEmptyAgendaDays] /// in [ScheduleViewSettings] disabled else it return min /// start date of the appointment collection. - _minDate = _getMinAppointmentDate(_appointments, widget.timeZone, - widget.minDate, currentMinDate, widget.scheduleViewSettings, _minWidth); + _minDate = _getMinAppointmentDate( + _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 @@ -3651,8 +4002,13 @@ class _SfCalendarState extends State /// 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, _minWidth); + _maxDate = _getMaxAppointmentDate( + _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 @@ -3820,7 +4176,7 @@ class _SfCalendarState extends State List dateAppointmentKeys = dateAppointments.keys.toList(); double labelHeight = 0; - if (!isWebView) { + if (_useMobilePlatformUI) { DateTime previousDate = addDuration(viewStartDate, const Duration(days: -1)); for (int i = 0; i < _nextDates.length; i++) { @@ -3841,7 +4197,7 @@ class _SfCalendarState extends State for (int i = 0; i < dateAppointmentKeys.length; i++) { final List currentDateAppointment = dateAppointments[dateAppointmentKeys[i]]; - if (!isWebView) { + if (_useMobilePlatformUI) { allDayCount += _getAllDayCount(currentDateAppointment); } @@ -3876,7 +4232,7 @@ class _SfCalendarState extends State widget.timeZone, false); - if (!isWebView) { + if (_useMobilePlatformUI) { final DateTime nextDate = _nextDates[1]; if (nextDate.month != viewStartDate.month) { labelHeight += @@ -3894,7 +4250,7 @@ class _SfCalendarState extends State for (int i = 0; i < dateAppointmentKeys.length; i++) { final List currentDateAppointment = dateAppointments[dateAppointmentKeys[i]]; - if (!isWebView) { + if (_useMobilePlatformUI) { allDayCount += _getAllDayCount(currentDateAppointment); } @@ -3965,7 +4321,7 @@ class _SfCalendarState extends State int allDayEventCount = 0; /// Web view does not differentiate all day and normal appointment. - if (!isWebView) { + if (_useMobilePlatformUI) { allDayEventCount = _getAllDayCount(currentDateAppointment); } @@ -3992,14 +4348,14 @@ class _SfCalendarState extends State totalAppointmentHeight + /// Add the divider height when it render on web. - (isWebView ? dateAppointmentKeys.length : 0) + - (isWebView + (!_useMobilePlatformUI ? dateAppointmentKeys.length : 0) + + (!_useMobilePlatformUI ? 0 : widget.scheduleViewSettings.weekHeaderSettings.height) + (viewStartDate.month == _controller.displayDate.month && viewStartDate.day != 1 ? 0 - : (isWebView + : (!_useMobilePlatformUI ? 0 : widget.scheduleViewSettings.monthHeaderSettings.height + padding)); @@ -4008,11 +4364,11 @@ class _SfCalendarState extends State ScrollController(initialScrollOffset: scrolledPosition) ..addListener(_handleScheduleViewScrolled); } else if ((viewStartDate.month != _controller.displayDate.month && - !isWebView) || + _useMobilePlatformUI) || todayNewEventHeight != 0) { _agendaScrollController.removeListener(_handleScheduleViewScrolled); _agendaScrollController = ScrollController( - initialScrollOffset: (isWebView + initialScrollOffset: (!_useMobilePlatformUI ? 0 : widget.scheduleViewSettings.weekHeaderSettings.height + padding) + @@ -4060,7 +4416,8 @@ class _SfCalendarState extends State _handleOnTapForHeader, _handleOnLongPressForHeader, widget.todayHighlightColor, - _textScaleFactor)), + _textScaleFactor, + _isMobilePlatform)), ), ), Positioned( @@ -4068,37 +4425,39 @@ class _SfCalendarState extends State left: 0, right: 0, height: height, - 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; - } - - return _getItem(context, index, isRTL); - }), - key: _scheduleViewKey, - ), - ], - )), + child: Opacity( + 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; + } + + return _getItem(context, index, isRTL); + }), + key: _scheduleViewKey, + ), + ], + ))), _addDatePicker(widget.headerHeight, isRTL), _getCalendarViewPopup(), ]); @@ -4196,37 +4555,36 @@ class _SfCalendarState extends State ? widget.headerStyle.textStyle.fontSize : _calendarTheme.headerTextStyle.fontSize; headerIconTextWidth ??= 14; - final bool isWebView = kIsWeb && _minWidth > _kMobileViewWidth; final double totalArrowWidth = 2 * arrowWidth; - final bool isCenterAlignment = kIsWeb && + final bool isCenterAlignment = !_isMobilePlatform && widget.headerStyle.textAlign != null && (widget.headerStyle.textAlign == TextAlign.center || widget.headerStyle.textAlign == TextAlign.justify); /// Calculate the calendar view button width that placed on header view - final double calendarViewWidth = isWebView - ? _getTextWidgetWidth(calendarViews[_view], widget.headerHeight, + final double calendarViewWidth = _useMobilePlatformUI + ? iconWidth + : _getTextWidgetWidth(calendarViews[_view], widget.headerHeight, _minWidth - totalArrowWidth, context, style: style) .width + padding + - headerIconTextWidth - : iconWidth; + headerIconTextWidth; double dividerWidth = 0; double todayWidth = 0; /// Today button shown only the date picker enabled. if (widget.showDatePickerButton) { - todayWidth = isWebView - ? _getTextWidgetWidth(_localizations.todayLabel, widget.headerHeight, + todayWidth = _useMobilePlatformUI + ? iconWidth + : _getTextWidgetWidth(_localizations.todayLabel, widget.headerHeight, _minWidth - totalArrowWidth, context, style: style) .width + - padding - : iconWidth; + padding; /// Divider shown when the view holds calendar views and today button. - dividerWidth = isWebView ? 5 : 0; + dividerWidth = _useMobilePlatformUI ? 0 : 5; } double headerWidth = _minWidth - totalArrowWidth - @@ -4240,17 +4598,43 @@ class _SfCalendarState extends State /// 20 as container left and right padding for the view. width += 20; double left = 0; - if (kIsWeb) { + + /// Specifies the popup animation start position. + Alignment popupAlignment; + if (_isMobilePlatform) { + /// icon width specifies the today button width and calendar view width. + left = _isRTL + ? totalArrowWidth + : headerWidth + todayWidth + iconWidth - width; + popupAlignment = _isRTL ? Alignment.topLeft : Alignment.topRight; + if (widget.headerStyle.textAlign == TextAlign.right || + widget.headerStyle.textAlign == TextAlign.end) { + popupAlignment = _isRTL ? Alignment.topRight : Alignment.topLeft; + left = _isRTL + ? headerWidth + iconWidth + todayWidth - width + : totalArrowWidth; + } else if (widget.headerStyle.textAlign == TextAlign.center || + widget.headerStyle.textAlign == TextAlign.justify) { + popupAlignment = _isRTL ? Alignment.topLeft : Alignment.topRight; + left = _isRTL + ? arrowWidth + : headerWidth + arrowWidth + todayWidth + iconWidth - width; + } + } else { left = _isRTL ? calendarViewWidth - width : headerWidth + totalArrowWidth + todayWidth + dividerWidth - 1; + popupAlignment = _isRTL ? Alignment.topLeft : Alignment.topRight; if (widget.headerStyle.textAlign == TextAlign.right || widget.headerStyle.textAlign == TextAlign.end) { + popupAlignment = _isRTL ? Alignment.topRight : Alignment.topLeft; left = _isRTL ? headerWidth + totalArrowWidth + todayWidth + dividerWidth - 1 : calendarViewWidth - width; } else if (widget.headerStyle.textAlign == TextAlign.center || widget.headerStyle.textAlign == TextAlign.justify) { + popupAlignment = _isRTL ? Alignment.topRight : Alignment.topLeft; + /// Calculate the left padding by calculate the total icon and header. /// Calculate the menu icon position by adding the left padding, left /// arrow and header label. @@ -4269,22 +4653,6 @@ class _SfCalendarState extends State todayWidth + dividerWidth; } - } else { - /// icon width specifies the today button width and calendar view width. - left = _isRTL - ? totalArrowWidth + iconWidth - width - : headerWidth + todayWidth; - if (widget.headerStyle.textAlign == TextAlign.right || - widget.headerStyle.textAlign == TextAlign.end) { - left = _isRTL - ? headerWidth + iconWidth + todayWidth - width - : totalArrowWidth; - } else if (widget.headerStyle.textAlign == TextAlign.center || - widget.headerStyle.textAlign == TextAlign.justify) { - left = _isRTL - ? arrowWidth + iconWidth - width - : headerWidth + arrowWidth + todayWidth; - } } if (left < 2) { @@ -4302,30 +4670,33 @@ class _SfCalendarState extends State ? scrollPosition : maxScrollPosition - height; } + return Positioned( top: widget.headerHeight, left: left, height: height, width: width, - child: Container( - padding: EdgeInsets.all(0), - decoration: BoxDecoration( - color: _calendarTheme.brightness != null && - _calendarTheme.brightness == Brightness.dark - ? Colors.grey[850] - : Colors.white, - boxShadow: kElevationToShadow[6], - borderRadius: BorderRadius.circular(2.0), - shape: BoxShape.rectangle, - ), - child: Material( - type: MaterialType.transparency, - child: ListView( - padding: EdgeInsets.all(0), - controller: - ScrollController(initialScrollOffset: scrollPosition), - children: children), - ))); + child: _PopupWidget( + alignment: popupAlignment, + child: Container( + padding: EdgeInsets.all(0), + decoration: BoxDecoration( + color: _calendarTheme.brightness != null && + _calendarTheme.brightness == Brightness.dark + ? Colors.grey[850] + : Colors.white, + boxShadow: kElevationToShadow[6], + borderRadius: BorderRadius.circular(2.0), + shape: BoxShape.rectangle, + ), + child: Material( + type: MaterialType.transparency, + child: ListView( + padding: EdgeInsets.all(0), + controller: + ScrollController(initialScrollOffset: scrollPosition), + children: children), + )))); } /// Adds the resource panel on the left side of the view, if the resource @@ -4383,28 +4754,113 @@ class _SfCalendarState extends State width: resourceViewSize, top: widget.headerHeight + top, bottom: 0, - child: ListView( - padding: const EdgeInsets.all(0.0), - physics: const ClampingScrollPhysics(), - controller: _resourcePanelScrollController, - scrollDirection: Axis.vertical, - children: [ - CustomPaint( - painter: _ResourceContainer( - widget.dataSource.resources, - widget.resourceViewSettings, - resourceItemHeight, - widget.cellBorderColor, - _calendarTheme, - _resourceImageNotifier, - isRTL, - _textScaleFactor), - size: Size(resourceViewSize, panelHeight), - ), - ])) + child: MouseRegion( + onEnter: (PointerEnterEvent event) { + _pointerEnterEvent(event, false, isRTL, null, + top + widget.headerHeight, 0, isResourceEnabled); + }, + onExit: _pointerExitEvent, + onHover: (PointerHoverEvent event) { + _pointerHoverEvent(event, false, isRTL, null, + top + widget.headerHeight, 0, isResourceEnabled); + }, + child: GestureDetector( + child: ListView( + padding: const EdgeInsets.all(0.0), + physics: const ClampingScrollPhysics(), + controller: _resourcePanelScrollController, + scrollDirection: Axis.vertical, + children: [ + CustomPaint( + painter: _ResourceContainer( + widget.dataSource.resources, + widget.resourceViewSettings, + resourceItemHeight, + widget.cellBorderColor, + _calendarTheme, + _resourceImageNotifier, + isRTL, + _textScaleFactor, + _resourceHoverNotifier.value), + size: Size(resourceViewSize, panelHeight), + ), + ]), + onTapUp: (TapUpDetails details) { + _handleOnTapForResourcePanel(details, resourceItemHeight); + }, + onLongPressStart: (LongPressStartDetails details) { + _handleOnLongPressForResourcePanel( + details, resourceItemHeight); + }, + ))) ])); } + /// Handles and raises the [widget.onLongPress] callback, when the resource + /// panel is long pressed in [SfCalendar]. + void _handleOnLongPressForResourcePanel( + LongPressStartDetails details, double resourceItemHeight) { + if (!_shouldRaiseCalendarLongPressCallback(widget.onLongPress)) { + return; + } + + final CalendarResource tappedResource = + _getTappedResource(details.localPosition.dy, resourceItemHeight); + final List resourceAppointments = + _getSelectedResourceAppointments(tappedResource); + _raiseCalendarLongPressCallback(widget, + element: CalendarElement.resourceHeader, + resource: tappedResource, + appointments: resourceAppointments); + } + + /// Handles and raises the [widget.onTap] callback, when the resource panel + /// is tapped in [SfCalendar]. + void _handleOnTapForResourcePanel( + TapUpDetails details, double resourceItemHeight) { + if (!_shouldRaiseCalendarTapCallback(widget.onTap)) { + return; + } + + final CalendarResource tappedResource = + _getTappedResource(details.localPosition.dy, resourceItemHeight); + final List resourceAppointments = + _getSelectedResourceAppointments(tappedResource); + _raiseCalendarTapCallback(widget, + element: CalendarElement.resourceHeader, + resource: tappedResource, + appointments: resourceAppointments); + } + + /// Filter and returns the appointment collection for the given resource from + /// the visible appointments collection. + List _getSelectedResourceAppointments(CalendarResource resource) { + final List selectedResourceAppointments = []; + if (_visibleAppointments == null || _visibleAppointments.isEmpty) { + return selectedResourceAppointments; + } + + for (int i = 0; i < _visibleAppointments.length; i++) { + final Appointment app = _visibleAppointments[i]; + if (app.resourceIds != null && + app.resourceIds.isNotEmpty && + app.resourceIds.contains(resource.id)) { + selectedResourceAppointments.add(app._data ?? app); + } + } + + return selectedResourceAppointments; + } + + /// Returns the tapped resource details, based on the tapped position. + CalendarResource _getTappedResource( + double tappedPosition, double resourceItemHeight) { + final int index = + (_resourcePanelScrollController.offset + tappedPosition) ~/ + resourceItemHeight; + return widget.dataSource.resources[index]; + } + /// Adds the custom scroll view which used to produce the infinity scroll. Widget _addCustomScrollView( double top, @@ -4419,27 +4875,30 @@ class _SfCalendarState extends State left: isResourceEnabled && !isRTL ? resourceViewSize : 0, right: isResourceEnabled && isRTL ? resourceViewSize : 0, height: height - agendaHeight, - child: _CustomScrollView( - widget, - _view, - width - resourceViewSize, - height - agendaHeight, - _visibleAppointments, - _agendaSelectedDate, - isRTL, - _locale, - _calendarTheme, - _timeZoneLoaded ? widget.specialRegions : null, - _blackoutDates, - _controller, - _removeDatePicker, - _resourcePanelScrollController, - _textScaleFactor, - updateCalendarState: (_UpdateCalendarStateDetails details) { - _updateCalendarState(details); - }, getCalendarState: (_UpdateCalendarStateDetails details) { - _getCalendarStateDetails(details); - }), + child: Opacity( + opacity: _opacity, + child: _CustomScrollView( + widget, + _view, + width - resourceViewSize, + height - agendaHeight, + _agendaSelectedDate, + isRTL, + _locale, + _calendarTheme, + _timeZoneLoaded ? widget.specialRegions : null, + _blackoutDates, + _controller, + _removeDatePicker, + _resourcePanelScrollController, + _textScaleFactor, + _isMobilePlatform, + _fadeInController, + updateCalendarState: (_UpdateCalendarStateDetails details) { + _updateCalendarState(details); + }, getCalendarState: (_UpdateCalendarStateDetails details) { + _getCalendarStateDetails(details); + })), ); } @@ -4488,7 +4947,8 @@ class _SfCalendarState extends State _handleOnTapForHeader, _handleOnLongPressForHeader, widget.todayHighlightColor, - _textScaleFactor)), + _textScaleFactor, + _isMobilePlatform)), ), _addResourcePanel(isResourceEnabled, resourceViewSize, height, isRTL), _addCustomScrollView(widget.headerHeight, resourceViewSize, isRTL, @@ -4516,15 +4976,9 @@ class _SfCalendarState extends State return; } - if (_showHeader) { - setState(() { - _showHeader = false; - }); - } else { - setState(() { - _showHeader = true; - }); - } + setState(() { + _showHeader = !_showHeader; + }); } Widget _addDatePicker(double top, bool isRTL) { @@ -4544,8 +4998,11 @@ class _SfCalendarState extends State final Color todayColor = widget.todayHighlightColor ?? _calendarTheme.todayHighlightColor; double left = 0; - if (kIsWeb) { - const padding = 5; + if (_isMobilePlatform) { + pickerWidth = _minWidth; + pickerHeight = _minHeight * 0.5; + } else { + const double padding = 5; double arrowWidth = 0; double iconWidth = _minWidth / 8; iconWidth = iconWidth > 40 ? 40 : iconWidth; @@ -4608,9 +5065,6 @@ class _SfCalendarState extends State : leftPadding + arrowWidth + headerPadding; } } - } else { - pickerWidth = _minWidth; - pickerHeight = _minHeight * 0.5; } return Positioned( @@ -4618,108 +5072,111 @@ class _SfCalendarState extends State left: left, width: pickerWidth, height: pickerHeight, - child: Container( - margin: EdgeInsets.all(0), - padding: EdgeInsets.all(5), - decoration: kIsWeb - ? BoxDecoration( - color: _calendarTheme.brightness != null && - _calendarTheme.brightness == Brightness.dark - ? Colors.grey[850] - : Colors.white, - boxShadow: kElevationToShadow[6], - borderRadius: BorderRadius.circular(2.0), - shape: BoxShape.rectangle, - ) - : BoxDecoration( - color: _calendarTheme.brightness != null && - _calendarTheme.brightness == Brightness.dark - ? Colors.grey[850] - : Colors.white, - boxShadow: [ - BoxShadow( - offset: Offset(0.0, 3.0), - blurRadius: 2.0, - spreadRadius: 0.0, - color: Color(0x24000000)), - ], - shape: BoxShape.rectangle, + child: _PopupWidget( + child: Container( + margin: EdgeInsets.all(0), + padding: EdgeInsets.all(5), + decoration: _isMobilePlatform + ? BoxDecoration( + color: _calendarTheme.brightness != null && + _calendarTheme.brightness == Brightness.dark + ? Colors.grey[850] + : Colors.white, + boxShadow: [ + BoxShadow( + offset: Offset(0.0, 3.0), + blurRadius: 2.0, + spreadRadius: 0.0, + color: Color(0x24000000)), + ], + shape: BoxShape.rectangle, + ) + : BoxDecoration( + color: _calendarTheme.brightness != null && + _calendarTheme.brightness == Brightness.dark + ? Colors.grey[850] + : Colors.white, + boxShadow: kElevationToShadow[6], + borderRadius: BorderRadius.circular(2.0), + shape: BoxShape.rectangle, + ), + child: SfDateRangePicker( + showNavigationArrow: true, + initialSelectedDate: _currentDate, + initialDisplayDate: _currentDate, + todayHighlightColor: todayColor, + minDate: widget.minDate, + maxDate: widget.maxDate, + selectionColor: todayColor, + headerStyle: DateRangePickerHeaderStyle( + textAlign: + _isMobilePlatform ? TextAlign.center : TextAlign.left, + ), + monthViewSettings: DateRangePickerMonthViewSettings( + viewHeaderHeight: pickerHeight / 8, + firstDayOfWeek: widget.firstDayOfWeek, + ), + monthCellStyle: DateRangePickerMonthCellStyle( + textStyle: datePickerStyle, + todayTextStyle: + datePickerStyle.copyWith(color: todayColor)), + yearCellStyle: DateRangePickerYearCellStyle( + textStyle: datePickerStyle, + todayTextStyle: datePickerStyle.copyWith(color: todayColor), + leadingDatesTextStyle: widget.monthViewSettings + .monthCellStyle.leadingDatesTextStyle ?? + _calendarTheme.leadingDatesTextStyle, ), - child: SfDateRangePicker( - showNavigationArrow: true, - initialSelectedDate: _currentDate, - initialDisplayDate: _currentDate, - todayHighlightColor: todayColor, - minDate: widget.minDate, - maxDate: widget.maxDate, - selectionColor: todayColor, - headerStyle: DateRangePickerHeaderStyle( - textAlign: kIsWeb ? TextAlign.left : TextAlign.center, - ), - monthViewSettings: DateRangePickerMonthViewSettings( - viewHeaderHeight: pickerHeight / 8, - firstDayOfWeek: widget.firstDayOfWeek, - ), - monthCellStyle: DateRangePickerMonthCellStyle( - textStyle: datePickerStyle, - todayTextStyle: datePickerStyle.copyWith(color: todayColor)), - yearCellStyle: DateRangePickerYearCellStyle( - textStyle: datePickerStyle, - todayTextStyle: datePickerStyle.copyWith(color: todayColor), - leadingDatesTextStyle: widget.monthViewSettings.monthCellStyle - .leadingDatesTextStyle ?? - _calendarTheme.leadingDatesTextStyle, - ), - view: _view == CalendarView.month || - _view == CalendarView.timelineMonth - ? DateRangePickerView.year - : DateRangePickerView.month, - onViewChanged: (DateRangePickerViewChangedArgs details) { - if ((_view != CalendarView.month && - _view != CalendarView.timelineMonth) || - details.view != DateRangePickerView.month) { - return; - } - - if (isSameDate(_currentDate, _controller.displayDate) || - isDateWithInDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[ - _currentViewVisibleDates.length - 1], - _controller.displayDate)) { - _removeDatePicker(); - } - - _showHeader = false; - _controller.displayDate = DateTime( - details.visibleDateRange.startDate.year, - details.visibleDateRange.startDate.month, - details.visibleDateRange.startDate.day, - _controller.displayDate.hour, - _controller.displayDate.minute, - _controller.displayDate.second); - }, - 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); - }, - ))); + 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(); + } + + _showHeader = false; + _controller.displayDate = DateTime( + details.visibleDateRange.startDate.year, + details.visibleDateRange.startDate.month, + details.visibleDateRange.startDate.day, + _controller.displayDate.hour, + _controller.displayDate.minute, + _controller.displayDate.second); + }, + 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); + }, + )))); } void _getCalendarStateDetails(_UpdateCalendarStateDetails details) { @@ -4744,6 +5201,7 @@ class _SfCalendarState extends State _currentViewVisibleDates = details._currentViewVisibleDates; _allDayAppointmentViewCollection = null; _visibleAppointments = null; + _allDayPanelHeight = 0; _updateVisibleAppointments(); if (_shouldRaiseViewChangedCallback(widget.onViewChanged)) { final bool showTrailingLeadingDates = _isLeadingAndTrailingDatesVisible( @@ -4951,34 +5409,29 @@ class _SfCalendarState extends State right: 0, left: 0, height: height, - child: Container( - color: widget.monthViewSettings.agendaStyle.backgroundColor ?? - _calendarTheme.agendaBackgroundColor, - child: MouseRegion( - onEnter: (PointerEnterEvent event) { - _pointerEnterEvent(event, isRTL); - }, - onExit: _pointerExitEvent, - onHover: (PointerHoverEvent event) { - _pointerHoverEvent(event, isRTL); - }, + child: Opacity( + opacity: _opacity, + child: Container( + color: widget.monthViewSettings.agendaStyle.backgroundColor ?? + _calendarTheme.agendaBackgroundColor, child: GestureDetector( - child: CustomPaint( - painter: _AgendaViewPainter( - widget.monthViewSettings, - null, - currentSelectedDate, - null, - isRTL, - _locale, - _localizations, - _calendarTheme, - _agendaViewNotifier, - widget.appointmentTimeTextFormat, - 0, - _textScaleFactor), - size: Size(width, height), - ), + child: _AgendaViewLayout( + widget.monthViewSettings, + null, + currentSelectedDate, + null, + isRTL, + _locale, + _localizations, + _calendarTheme, + _agendaViewNotifier, + widget.appointmentTimeTextFormat, + 0, + _textScaleFactor, + _isMobilePlatform, + widget.appointmentBuilder, + width, + height), onTapUp: (TapUpDetails details) { _handleTapForAgenda(details, null); }, @@ -4990,6 +5443,12 @@ class _SfCalendarState extends State final List agendaAppointments = _getSelectedDateAppointments( _appointments, widget.timeZone, currentSelectedDate); + agendaAppointments.sort((Appointment app1, Appointment app2) => + app1._actualStartTime.compareTo(app2._actualStartTime)); + agendaAppointments.sort((Appointment app1, Appointment app2) => + _orderAppointmentsAscending(app1.isAllDay, app2.isAllDay)); + agendaAppointments.sort((Appointment app1, Appointment app2) => + _orderAppointmentsAscending(app1._isSpanned, app2._isSpanned)); /// Each appointment have top padding and it used to show the space /// between two appointment views @@ -5017,47 +5476,48 @@ class _SfCalendarState extends State right: 0, left: 0, height: height, - child: Container( - color: widget.monthViewSettings.agendaStyle.backgroundColor ?? - _calendarTheme.agendaBackgroundColor, - child: MouseRegion( - onEnter: (PointerEnterEvent event) { - _pointerEnterEvent(event, isRTL); - }, - onExit: _pointerExitEvent, - onHover: (PointerHoverEvent event) { - _pointerHoverEvent(event, 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), - size: Size(_agendaDateViewWidth, height), - ), - Positioned( - top: 0, - left: isRTL ? 0 : _agendaDateViewWidth, - right: isRTL ? _agendaDateViewWidth : 0, - bottom: 0, - child: ListView( - padding: const EdgeInsets.all(0.0), - controller: _agendaScrollController, - children: [ - CustomPaint( - painter: _AgendaViewPainter( + child: Opacity( + opacity: _opacity, + child: Container( + color: widget.monthViewSettings.agendaStyle.backgroundColor ?? + _calendarTheme.agendaBackgroundColor, + child: MouseRegion( + onEnter: (PointerEnterEvent event) { + _pointerEnterEvent(event, false, isRTL); + }, + onExit: _pointerExitEvent, + onHover: (PointerHoverEvent event) { + _pointerHoverEvent(event, false, isRTL); + }, + child: GestureDetector( + child: Stack(children: [ + CustomPaint( + painter: _AgendaDateTimePainter( + currentSelectedDate, + widget.monthViewSettings, + null, + widget.todayHighlightColor ?? + _calendarTheme.todayHighlightColor, + widget.todayTextStyle, + _locale, + _calendarTheme, + _agendaDateNotifier, + _minWidth, + isRTL, + _textScaleFactor, + _isMobilePlatform), + size: Size(_agendaDateViewWidth, height), + ), + Positioned( + top: 0, + left: isRTL ? 0 : _agendaDateViewWidth, + right: isRTL ? _agendaDateViewWidth : 0, + bottom: 0, + child: ListView( + padding: const EdgeInsets.all(0.0), + controller: _agendaScrollController, + children: [ + _AgendaViewLayout( widget.monthViewSettings, null, currentSelectedDate, @@ -5069,21 +5529,72 @@ class _SfCalendarState extends State _agendaViewNotifier, widget.appointmentTimeTextFormat, _agendaDateViewWidth, - _textScaleFactor), - size: Size( - width - _agendaDateViewWidth, painterHeight), - ), - ], + _textScaleFactor, + _isMobilePlatform, + widget.appointmentBuilder, + width - _agendaDateViewWidth, + painterHeight), + ], + ), ), - ), - ], - ), - onTapUp: (TapUpDetails details) { - _handleTapForAgenda(details, _selectedDate); - }, - onLongPressStart: (LongPressStartDetails details) { - _handleLongPressForAgenda(details, _selectedDate); - }, - )))); + ]), + onTapUp: (TapUpDetails details) { + _handleTapForAgenda(details, _selectedDate); + }, + onLongPressStart: (LongPressStartDetails details) { + _handleLongPressForAgenda(details, _selectedDate); + }, + ))))); + } +} + +/// Widget used to show the pop up animation to the child. +class _PopupWidget extends StatefulWidget { + _PopupWidget({this.child, this.alignment = Alignment.topCenter}); + + /// Widget that animated like popup. + final Widget child; + + /// Alignment defines the popup animation start position. + final Alignment alignment; + + @override + State createState() => _PopupWidgetState(); +} + +class _PopupWidgetState extends State<_PopupWidget> + with SingleTickerProviderStateMixin { + /// Controller used to handle the animation. + AnimationController _animationController; + + /// Popup animation used to show the child like popup. + Animation _animation; + + @override + void initState() { + _animationController = + AnimationController(vsync: this, duration: Duration(milliseconds: 200)); + _animation = + CurvedAnimation(parent: _animationController, curve: Curves.easeInOut); + super.initState(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + /// Reset the existing animation. + _animationController.reset(); + + /// Start the animation. + _animationController.forward(); + return ScaleTransition( + alignment: widget.alignment, + scale: _animation, + child: FadeTransition(opacity: _animation, child: widget.child)); } } 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 6f0865836..bff11d49d 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 @@ -20,6 +20,7 @@ class _CalendarView extends StatefulWidget { this.resourcePanelScrollController, this.resourceCollection, this.textScaleFactor, + this.isMobilePlatform, {Key key, this.updateCalendarState, this.getCalendarState}) @@ -44,6 +45,7 @@ class _CalendarView extends StatefulWidget { final ScrollController resourcePanelScrollController; final List resourceCollection; final double textScaleFactor; + final bool isMobilePlatform; @override _CalendarViewState createState() => _CalendarViewState(); @@ -63,9 +65,7 @@ class _CalendarViewState extends State<_CalendarView> _timelineViewVerticalScrollController, _timelineRulerController; - // scroll physics to handle the scrolling for time line view - ScrollPhysics _scrollPhysics; - _AppointmentPainter _appointmentPainter; + GlobalKey<_AppointmentLayoutState> _appointmentLayoutKey; AnimationController _timelineViewAnimationController; Animation _timelineViewAnimation; Tween _timelineViewTween; @@ -81,9 +81,7 @@ class _CalendarViewState extends State<_CalendarView> _calendarCellNotifier, _allDayNotifier, _appointmentHoverNotifier; - ValueNotifier _selectionNotifier, - _timelineViewHeaderNotifier, - _appointmentNotifier; + ValueNotifier _selectionNotifier, _timelineViewHeaderNotifier; bool _isRTL; bool _isExpanded = false; @@ -104,18 +102,15 @@ class _CalendarViewState extends State<_CalendarView> @override void initState() { _isExpanded = false; + _appointmentLayoutKey = GlobalKey<_AppointmentLayoutState>(); _hoveringDate = DateTime.now(); _selectionNotifier = ValueNotifier(false); - _appointmentNotifier = ValueNotifier(false); _timelineViewHeaderNotifier = ValueNotifier(false); _viewHeaderNotifier = ValueNotifier(null) ..addListener(_timelineViewHoveringUpdate); - _calendarCellNotifier = ValueNotifier(null) - ..addListener(_timelineViewHoveringUpdate); - _allDayNotifier = ValueNotifier(null) - ..addListener(_allDayPanelHoveringUpdate); - _appointmentHoverNotifier = ValueNotifier(null) - ..addListener(_appointmentHoverUpdate); + _calendarCellNotifier = ValueNotifier(null); + _allDayNotifier = ValueNotifier(null); + _appointmentHoverNotifier = ValueNotifier(null); _allDaySelectionNotifier = ValueNotifier<_SelectionDetails>(null); if (!_isTimelineView(widget.view) && widget.view != CalendarView.month) { _animationController = AnimationController( @@ -142,8 +137,14 @@ class _CalendarViewState extends State<_CalendarView> } _updateCalendarStateDetails = _UpdateCalendarStateDetails(); - _timeIntervalHeight = _getTimeIntervalHeight(widget.calendar, widget.view, - widget.width, widget.height, widget.visibleDates.length, _allDayHeight); + _timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + _allDayHeight, + widget.isMobilePlatform); if (widget.view != CalendarView.month) { _horizontalLinesCount = _getHorizontalLinesCount( widget.calendar.timeSlotViewSettings, widget.view); @@ -154,7 +155,6 @@ class _CalendarViewState extends State<_CalendarView> _timelineRulerController = ScrollController(initialScrollOffset: 0, keepScrollOffset: true) ..addListener(_timeRulerListener); - _scrollPhysics = const NeverScrollableScrollPhysics(); _timelineViewHeaderScrollController = ScrollController(initialScrollOffset: 0, keepScrollOffset: true); _timelineViewAnimationController = AnimationController( @@ -220,8 +220,22 @@ class _CalendarViewState extends State<_CalendarView> _scrollToPosition(); } - _timeIntervalHeight = _getTimeIntervalHeight(widget.calendar, widget.view, - widget.width, widget.height, widget.visibleDates.length, _allDayHeight); + _timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + _allDayHeight, + widget.isMobilePlatform); + + /// Clear the all day panel selection when the calendar view changed + /// Eg., if select the all day panel and switch to month view and again + /// select the same month cell and move to day view then the view show + /// calendar cell selection and all day panel selection. + if (oldWidget.view != widget.view) { + _allDaySelectionNotifier = ValueNotifier<_SelectionDetails>(null); + } if ((oldWidget.view != widget.view || oldWidget.width != widget.width || @@ -280,14 +294,6 @@ class _CalendarViewState extends State<_CalendarView> _calendarCellNotifier.removeListener(_timelineViewHoveringUpdate); } - if (_allDayNotifier != null) { - _allDayNotifier.removeListener(_allDayPanelHoveringUpdate); - } - - if (_appointmentHoverNotifier != null) { - _appointmentHoverNotifier.removeListener(_appointmentHoverUpdate); - } - if (_timelineViewAnimation != null) { _timelineViewAnimation.removeListener(_scrollAnimationListener); } @@ -383,11 +389,13 @@ class _CalendarViewState extends State<_CalendarView> Widget _getDayView() { _allDayHeight = 0; + final bool isCurrentView = + _updateCalendarStateDetails._currentViewVisibleDates == + widget.visibleDates; if (widget.view == CalendarView.day) { final double viewHeaderHeight = _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); - if (widget.visibleDates == - _updateCalendarStateDetails._currentViewVisibleDates) { + if (isCurrentView) { _allDayHeight = _kAllDayLayoutHeight > viewHeaderHeight && _updateCalendarStateDetails._allDayPanelHeight > viewHeaderHeight @@ -402,11 +410,7 @@ class _CalendarViewState extends State<_CalendarView> } else { _allDayHeight = viewHeaderHeight; } - } - - if (widget.view != CalendarView.day && - widget.visibleDates == - _updateCalendarStateDetails._currentViewVisibleDates) { + } else if (isCurrentView) { _allDayHeight = _updateCalendarStateDetails._allDayPanelHeight > _kAllDayLayoutHeight ? _kAllDayLayoutHeight @@ -419,8 +423,12 @@ class _CalendarViewState extends State<_CalendarView> onEnter: _pointerEnterEvent, onHover: _pointerHoverEvent, onExit: _pointerExitEvent, - child: _addDayView(widget.width, - _timeIntervalHeight * _horizontalLinesCount, _isRTL, widget.locale), + child: _addDayView( + widget.width, + _timeIntervalHeight * _horizontalLinesCount, + _isRTL, + widget.locale, + isCurrentView), ), onTapUp: (TapUpDetails details) { _handleOnTapForDay(details); @@ -432,13 +440,6 @@ class _CalendarViewState extends State<_CalendarView> } Widget _getTimelineView() { - if ((_scrollController.hasClients && !_scrollController.position.atEdge) && - _scrollPhysics.toString() == 'NeverScrollableScrollPhysics' && - _updateCalendarStateDetails._currentViewVisibleDates == - widget.visibleDates) { - _scrollPhysics = const ClampingScrollPhysics(); - } - return GestureDetector( child: MouseRegion( onEnter: _pointerEnterEvent, @@ -464,28 +465,8 @@ class _CalendarViewState extends State<_CalendarView> return; } - setState(() { - /* Updates the mouse hovering position to timeline views */ - }); - } - - void _appointmentHoverUpdate() { - if (!mounted) { - return; - } - - setState(() { - /*Updates the mouse hovering position to appointment UI */ - }); - } - - void _allDayPanelHoveringUpdate() { - if (!_isTimelineView(widget.view) && mounted) { - setState(() { - /* Updates the mouse hovering position to the all day - panel*/ - }); - } + // Updates the timeline views based on mouse hovering position. + _timelineViewHeaderNotifier.value = !_timelineViewHeaderNotifier.value; } void _scrollAnimationListener() { @@ -548,6 +529,102 @@ class _CalendarViewState extends State<_CalendarView> return timeToPosition; } + /// Used to retain the scrolled date time. + void _retainScrolledDateTime() { + if (widget.view == CalendarView.month) { + return; + } + + DateTime scrolledDate = widget.visibleDates[0]; + double scrolledPosition = 0; + if (_isTimelineView(widget.view)) { + final double singleViewWidth = _getSingleViewWidthForTimeLineView(this); + + /// Calculate the scrolled position date. + scrolledDate = widget + .visibleDates[_scrollController.position.pixels ~/ singleViewWidth]; + + /// Calculate the scrolled hour position without visible date position. + scrolledPosition = _scrollController.position.pixels % singleViewWidth; + } else { + /// Calculate the scrolled hour position. + scrolledPosition = _scrollController.position.pixels; + } + + /// Calculate the current horizontal line based on time interval height. + final double columnIndex = scrolledPosition / _timeIntervalHeight; + + /// Calculate the time based on calculated horizontal position. + final double time = + ((_getTimeInterval(widget.calendar.timeSlotViewSettings) / 60) * + columnIndex) + + widget.calendar.timeSlotViewSettings.startHour; + final int hour = time.toInt(); + final int minute = ((time - hour) * 60).round(); + scrolledDate = DateTime( + scrolledDate.year, scrolledDate.month, scrolledDate.day, hour, minute); + + /// Update the scrolled position after the widget generated. + SchedulerBinding.instance.addPostFrameCallback((_) { + _scrollController.jumpTo(_getPositionFromDate(scrolledDate)); + }); + } + + /// Calculate the position from date. + double _getPositionFromDate(DateTime date) { + final int visibleDatesCount = widget.visibleDates.length; + _timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + visibleDatesCount, + _allDayHeight, + widget.isMobilePlatform); + double timeToPosition = 0; + final bool isTimelineView = _isTimelineView(widget.view); + if (!isTimelineView) { + timeToPosition = + _timeToPosition(widget.calendar, date, _timeIntervalHeight); + } else { + for (int i = 0; i < visibleDatesCount; i++) { + if (!isSameDate(date, widget.visibleDates[i])) { + continue; + } + + if (widget.view == CalendarView.timelineMonth) { + timeToPosition = _timeIntervalHeight * i; + } else { + timeToPosition = (_getSingleViewWidthForTimeLineView(this) * i) + + _timeToPosition(widget.calendar, date, _timeIntervalHeight); + } + + break; + } + } + + double maxScrollPosition = 0; + if (!isTimelineView) { + final double scrollViewHeight = widget.height - + _allDayHeight - + _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); + final double scrollViewContentHeight = _getHorizontalLinesCount( + widget.calendar.timeSlotViewSettings, widget.view) * + _timeIntervalHeight; + maxScrollPosition = scrollViewContentHeight - scrollViewHeight; + } else { + final double scrollViewContentWidth = _getHorizontalLinesCount( + widget.calendar.timeSlotViewSettings, widget.view) * + _timeIntervalHeight * + visibleDatesCount; + maxScrollPosition = scrollViewContentWidth - widget.width; + } + + return maxScrollPosition > timeToPosition + ? timeToPosition + : maxScrollPosition; + } + void _expandOrCollapseAllDay() { _isExpanded = !_isExpanded; if (_isExpanded) { @@ -577,21 +654,6 @@ class _CalendarViewState extends State<_CalendarView> if (_isTimelineView(widget.view)) { widget.getCalendarState(_updateCalendarStateDetails); - if (_scrollController.position.atEdge && - _updateCalendarStateDetails._currentViewVisibleDates == - widget.visibleDates) { - setState(() { - _scrollPhysics = const NeverScrollableScrollPhysics(); - }); - } else if ((!_scrollController.position.atEdge) && - _scrollPhysics.toString() == 'NeverScrollableScrollPhysics' && - _updateCalendarStateDetails._currentViewVisibleDates == - widget.visibleDates) { - setState(() { - _scrollPhysics = const ClampingScrollPhysics(); - }); - } - if (_timelineViewHeader != null && widget.view != CalendarView.timelineMonth) { _timelineViewHeaderNotifier.value = !_timelineViewHeaderNotifier.value; @@ -605,107 +667,6 @@ class _CalendarViewState extends State<_CalendarView> } } - bool _isUpdateTimelineViewScroll(double initial, double dx) { - if (_scrollController.position.maxScrollExtent == 0) { - return false; - } - - double scrollToPosition = 0; - - if (_scrollController.offset < _scrollController.position.maxScrollExtent && - _scrollController.offset <= - _scrollController.position.viewportDimension && - (_isRTL ? initial < dx : initial > dx)) { - setState(() { - _scrollPhysics = const ClampingScrollPhysics(); - }); - - scrollToPosition = _isRTL ? dx - initial : initial - dx; - scrollToPosition = - scrollToPosition <= _scrollController.position.maxScrollExtent - ? scrollToPosition - : _scrollController.position.maxScrollExtent; - - _scrollController.jumpTo(scrollToPosition); - return true; - } else if (_scrollController.offset > - _scrollController.position.minScrollExtent && - _scrollController.offset != 0 && - (_isRTL ? initial > dx : initial < dx)) { - setState(() { - _scrollPhysics = const ClampingScrollPhysics(); - }); - scrollToPosition = _isRTL - ? _scrollController.position.maxScrollExtent - (initial - dx) - : _scrollController.position.maxScrollExtent - (dx - initial); - scrollToPosition = - scrollToPosition >= _scrollController.position.minScrollExtent - ? scrollToPosition - : _scrollController.position.minScrollExtent; - _scrollController.jumpTo(scrollToPosition); - return true; - } - - return false; - } - - bool _isAnimateTimelineViewScroll(double position, double velocity) { - if (_scrollController.position.maxScrollExtent == 0) { - return false; - } - - velocity = - velocity == 0 ? position.abs() : velocity / window.devicePixelRatio; - if (_scrollController.offset < _scrollController.position.maxScrollExtent && - _scrollController.offset <= - _scrollController.position.viewportDimension && - (_isRTL ? position > 0 : position < 0)) { - setState(() { - _scrollPhysics = const ClampingScrollPhysics(); - }); - - // animation to animate the scroll view manually in time line view - _timelineViewTween.begin = _scrollController.offset; - _timelineViewTween.end = - velocity.abs() > _scrollController.position.maxScrollExtent - ? _scrollController.position.maxScrollExtent - : velocity.abs(); - - if (_timelineViewAnimationController.isCompleted && - _scrollController.offset != _timelineViewTween.end) { - _timelineViewAnimationController.reset(); - } - - _timelineViewAnimationController.forward(); - return true; - } else if (_scrollController.offset > - _scrollController.position.minScrollExtent && - _scrollController.offset != 0 && - (_isRTL ? position < 0 : position > 0)) { - setState(() { - _scrollPhysics = const ClampingScrollPhysics(); - }); - - // animation to animate the scroll view manually in time line view - _timelineViewTween.begin = _scrollController.offset; - _timelineViewTween.end = - _scrollController.position.maxScrollExtent - velocity.abs() < - _scrollController.position.minScrollExtent - ? _scrollController.position.minScrollExtent - : _scrollController.position.maxScrollExtent - velocity.abs(); - - if (_timelineViewAnimationController.isCompleted && - _scrollController.offset != _timelineViewTween.end) { - _timelineViewAnimationController.reset(); - } - - _timelineViewAnimationController.forward(); - return true; - } - - return false; - } - void _updateTimeSlotView(_CalendarView oldWidget) { _animationController ??= AnimationController( duration: const Duration(milliseconds: 200), vsync: this); @@ -764,8 +725,6 @@ class _CalendarViewState extends State<_CalendarView> ScrollController(initialScrollOffset: 0, keepScrollOffset: true) ..addListener(_timeRulerListener); - _scrollPhysics = const NeverScrollableScrollPhysics(); - _timelineViewAnimationController = _timelineViewAnimationController ?? AnimationController( duration: const Duration(milliseconds: 300), @@ -798,7 +757,8 @@ class _CalendarViewState extends State<_CalendarView> details._selectedDate = _updateCalendarStateDetails._selectedDate; } - Widget _addAllDayAppointmentPanel(SfCalendarThemeData calendarTheme) { + Widget _addAllDayAppointmentPanel( + SfCalendarThemeData calendarTheme, bool isCurrentView) { final Color borderColor = widget.calendar.cellBorderColor ?? calendarTheme.cellBorderColor; final Widget shadowView = Divider( @@ -829,8 +789,9 @@ class _CalendarViewState extends State<_CalendarView> topPosition = 0; } - double panelHeight = - _updateCalendarStateDetails._allDayPanelHeight - _allDayHeight; + double panelHeight = isCurrentView + ? _updateCalendarStateDetails._allDayPanelHeight - _allDayHeight + : 0; if (panelHeight < 0) { panelHeight = 0; } @@ -853,40 +814,37 @@ class _CalendarViewState extends State<_CalendarView> physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.all(0.0), children: [ - CustomPaint( - painter: _AllDayAppointmentPainter( - widget.calendar, - widget.view, - widget.visibleDates, - widget.visibleDates == - _updateCalendarStateDetails - ._currentViewVisibleDates - ? _updateCalendarStateDetails._visibleAppointments - : null, - timeLabelWidth, - allDayExpanderHeight, - _updateCalendarStateDetails._allDayPanelHeight > - _allDayHeight && - (_heightAnimation.value == 1 || - widget.view == CalendarView.day), - _allDayExpanderAnimation.value != 0.0 && - _allDayExpanderAnimation.value != 1, - _isRTL, - widget.calendarTheme, - _allDaySelectionNotifier, - _allDayNotifier.value, - widget.textScaleFactor, updateCalendarState: - (_UpdateCalendarStateDetails details) { - _getPainterProperties(details); - }), - size: Size( - widget.width, - widget.view == CalendarView.day && - _updateCalendarStateDetails._allDayPanelHeight < - _allDayHeight - ? _allDayHeight - : _updateCalendarStateDetails._allDayPanelHeight), - ), + _AllDayAppointmentLayout( + widget.calendar, + widget.view, + widget.visibleDates, + widget.visibleDates == + _updateCalendarStateDetails._currentViewVisibleDates + ? _updateCalendarStateDetails._visibleAppointments + : null, + timeLabelWidth, + allDayExpanderHeight, + panelHeight > 0 && + (_heightAnimation.value == 1 || + widget.view == CalendarView.day), + _allDayExpanderAnimation.value != 0.0 && + _allDayExpanderAnimation.value != 1, + _isRTL, + widget.calendarTheme, + _allDaySelectionNotifier, + _allDayNotifier, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.width, + (widget.view == CalendarView.day && + _updateCalendarStateDetails._allDayPanelHeight < + _allDayHeight) || + !isCurrentView + ? _allDayHeight + : _updateCalendarStateDetails._allDayPanelHeight, + updateCalendarState: (_UpdateCalendarStateDetails details) { + _getPainterProperties(details); + }), ], ), ), @@ -901,28 +859,30 @@ class _CalendarViewState extends State<_CalendarView> ); } - _AppointmentPainter _addAppointmentPainter([double resourceItemHeight]) { - _appointmentPainter = _AppointmentPainter( - widget.calendar, - widget.view, - widget.visibleDates, - widget.visibleDates == - _updateCalendarStateDetails._currentViewVisibleDates - ? _updateCalendarStateDetails._visibleAppointments - : null, - _timeIntervalHeight, - _appointmentNotifier, - widget.calendarTheme, - _isRTL, - _appointmentHoverNotifier.value, - widget.resourceCollection, - resourceItemHeight, - widget.textScaleFactor, - updateCalendarState: (_UpdateCalendarStateDetails details) { - _getPainterProperties(details); - }); - - return _appointmentPainter; + _AppointmentLayout _addAppointmentPainter(double width, double height, + [double resourceItemHeight]) { + final List visibleAppointments = widget.visibleDates == + _updateCalendarStateDetails._currentViewVisibleDates + ? _updateCalendarStateDetails._visibleAppointments + : null; + return _AppointmentLayout( + widget.calendar, + widget.view, + widget.visibleDates, + ValueNotifier>(visibleAppointments), + _timeIntervalHeight, + widget.calendarTheme, + _isRTL, + _appointmentHoverNotifier, + widget.resourceCollection, + resourceItemHeight, + widget.textScaleFactor, + widget.isMobilePlatform, + width, + height, + _getPainterProperties, + key: _appointmentLayoutKey, + ); } // Returns the month view as a child for the calendar view. @@ -974,12 +934,15 @@ class _CalendarViewState extends State<_CalendarView> right: 0, bottom: 0, child: RepaintBoundary( - child: CustomPaint( - child: _getMonthWidget(isRTL, height), - size: Size(widget.width, height), - foregroundPainter: _addAppointmentPainter(), - ), - ), + child: _CalendarMultiChildContainer( + width: widget.width, + height: height, + children: [ + RepaintBoundary(child: _getMonthWidget(isRTL, height)), + RepaintBoundary( + child: _addAppointmentPainter(widget.width, height)), + ], + )), ), Positioned( left: 0, @@ -1029,7 +992,8 @@ class _CalendarViewState extends State<_CalendarView> } // Returns the day view as a child for the calendar view. - Widget _addDayView(double width, double height, bool isRTL, String locale) { + Widget _addDayView(double width, double height, bool isRTL, String locale, + bool isCurrentView) { double viewHeaderWidth = widget.width; double viewHeaderHeight = _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); @@ -1041,8 +1005,9 @@ class _CalendarViewState extends State<_CalendarView> _allDayHeight > viewHeaderHeight ? _allDayHeight : viewHeaderHeight; } - double panelHeight = - _updateCalendarStateDetails._allDayPanelHeight - _allDayHeight; + double panelHeight = isCurrentView + ? _updateCalendarStateDetails._allDayPanelHeight - _allDayHeight + : 0; if (panelHeight < 0) { panelHeight = 0; } @@ -1051,6 +1016,7 @@ class _CalendarViewState extends State<_CalendarView> panelHeight * _allDayExpanderAnimation.value; return Stack( children: [ + _addAllDayAppointmentPanel(widget.calendarTheme, isCurrentView), Positioned( left: isRTL ? widget.width - viewHeaderWidth : 0, top: 0, @@ -1088,7 +1054,6 @@ class _CalendarViewState extends State<_CalendarView> ), ), ), - _addAllDayAppointmentPanel(widget.calendarTheme), Positioned( top: (widget.view == CalendarView.day) ? viewHeaderHeight + allDayExpanderHeight @@ -1106,23 +1071,30 @@ class _CalendarViewState extends State<_CalendarView> children: [ Stack(children: [ RepaintBoundary( - child: CustomPaint( - painter: _TimeSlotView( - widget.visibleDates, - _horizontalLinesCount, - _timeIntervalHeight, - timeLabelWidth, - widget.calendar.cellBorderColor, - widget.calendarTheme, - widget.calendar.timeSlotViewSettings, - isRTL, - widget.regions, - _calendarCellNotifier, - widget.textScaleFactor), - size: Size(width, height), - foregroundPainter: _addAppointmentPainter(), - ), - ), + child: _CalendarMultiChildContainer( + width: width, + height: height, + children: [ + RepaintBoundary( + child: _TimeSlotWidget( + widget.visibleDates, + _horizontalLinesCount, + _timeIntervalHeight, + timeLabelWidth, + widget.calendar.cellBorderColor, + widget.calendarTheme, + widget.calendar.timeSlotViewSettings, + isRTL, + widget.regions, + _calendarCellNotifier, + widget.textScaleFactor, + widget.calendar.timeRegionBuilder, + width, + height), + ), + RepaintBoundary( + child: _addAppointmentPainter(width, height)), + ])), RepaintBoundary( child: CustomPaint( painter: _TimeRulerView( @@ -1210,7 +1182,7 @@ class _CalendarViewState extends State<_CalendarView> padding: const EdgeInsets.all(0.0), controller: _timelineRulerController, scrollDirection: Axis.horizontal, - physics: _scrollPhysics, + physics: _CustomNeverScrollableScrollPhysics(), children: [ RepaintBoundary( child: CustomPaint( @@ -1239,7 +1211,7 @@ class _CalendarViewState extends State<_CalendarView> padding: const EdgeInsets.all(0.0), controller: _scrollController, scrollDirection: Axis.horizontal, - physics: _scrollPhysics, + physics: _CustomNeverScrollableScrollPhysics(), children: [ Container( width: width, @@ -1256,27 +1228,35 @@ class _CalendarViewState extends State<_CalendarView> children: [ Stack(children: [ RepaintBoundary( - child: CustomPaint( - painter: _TimelineView( - _horizontalLinesCount, - widget.visibleDates, - widget.calendar.timeSlotViewSettings, - _timeIntervalHeight, - widget.calendar.cellBorderColor, - _isRTL, - widget.locale, - widget.calendarTheme, - _calendarCellNotifier, - _scrollController, - widget.regions, - resourceItemHeight, - widget.resourceCollection, - widget.textScaleFactor), - size: Size(width, height), - foregroundPainter: _addAppointmentPainter( - resourceItemHeight), - ), - ), + child: _CalendarMultiChildContainer( + width: width, + height: height, + children: [ + RepaintBoundary( + child: _TimelineWidget( + _horizontalLinesCount, + widget.visibleDates, + widget + .calendar.timeSlotViewSettings, + _timeIntervalHeight, + widget.calendar.cellBorderColor, + _isRTL, + widget.calendarTheme, + _calendarCellNotifier, + _scrollController, + widget.regions, + resourceItemHeight, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.calendar.timeRegionBuilder, + width, + height)), + RepaintBoundary( + child: _addAppointmentPainter( + width, height, resourceItemHeight)) + ], + )), RepaintBoundary( child: CustomPaint( painter: @@ -1327,14 +1307,11 @@ class _CalendarViewState extends State<_CalendarView> _AppointmentView appointmentView; bool isMoreTapped = false; - if (kIsWeb && - widget.width > _kMobileViewWidth && + if (!widget.isMobilePlatform && widget.calendar.monthViewSettings.appointmentDisplayMode == MonthAppointmentDisplayMode.appointment) { - appointmentView = _getAppointmentOnPoint( - _appointmentPainter._appointmentCollection, - xDetails, - yDetails - viewHeaderHeight); + appointmentView = _appointmentLayoutKey.currentState + ._getAppointmentViewOnPoint(xDetails, yDetails - viewHeaderHeight); isMoreTapped = appointmentView != null && appointmentView.startIndex == -1 && appointmentView.endIndex == -1 && @@ -1383,18 +1360,12 @@ class _CalendarViewState extends State<_CalendarView> widget.calendar.onLongPress)) || (isTapCallback && _shouldRaiseCalendarTapCallback(widget.calendar.onTap))) { - final List selectedAppointments = - appointmentView == null || isMoreTapped - ? _getSelectedAppointments(selectedDate) - : (_isCalendarAppointment(widget.calendar.dataSource) - ? [ - appointmentView.appointment._data ?? - appointmentView.appointment - ] - : _getCustomAppointments([ - appointmentView.appointment._data ?? - appointmentView.appointment - ])); + final List selectedAppointments = appointmentView == null || + isMoreTapped + ? _getSelectedAppointments(selectedDate) + : [ + appointmentView.appointment._data ?? appointmentView.appointment + ]; final CalendarElement selectedElement = appointmentView == null ? CalendarElement.calendarCell : isMoreTapped @@ -1515,8 +1486,9 @@ class _CalendarViewState extends State<_CalendarView> xPosition; } - final _AppointmentView appointmentView = _getAppointmentOnPoint( - _appointmentPainter._appointmentCollection, xPosition, yPosition); + final _AppointmentView appointmentView = _appointmentLayoutKey + .currentState + ._getAppointmentViewOnPoint(xPosition, yPosition); if (appointmentView == null) { _drawSelection(xDetails, yPosition, timeLabelWidth); selectedDate = _selectionPainter.selectedDate; @@ -1704,7 +1676,7 @@ class _CalendarViewState extends State<_CalendarView> } final double yPosition = yDetails - viewHeaderHeight; - final _AppointmentView appointmentView = _getAppointmentOnPoint( + final _AppointmentView appointmentView = _getAllDayAppointmentOnPoint( _updateCalendarStateDetails._allDayAppointmentViewCollection, xDetails, yPosition); @@ -1817,8 +1789,9 @@ class _CalendarViewState extends State<_CalendarView> } else { final double yPosition = yDetails - viewHeaderHeight - allDayHeight + _scrollController.offset; - final _AppointmentView appointmentView = _getAppointmentOnPoint( - _appointmentPainter._appointmentCollection, xDetails, yPosition); + final _AppointmentView appointmentView = _appointmentLayoutKey + .currentState + ._getAppointmentViewOnPoint(xDetails, yPosition); _allDaySelectionNotifier?.value = null; if (appointmentView == null) { if (_isRTL) { @@ -1935,7 +1908,8 @@ class _CalendarViewState extends State<_CalendarView> widget.width, widget.height, widget.visibleDates.length, - _allDayHeight); + _allDayHeight, + widget.isMobilePlatform); final double minuteHeight = timeIntervalSize / _getTimeInterval(widget.calendar.timeSlotViewSettings); @@ -2112,6 +2086,10 @@ class _CalendarViewState extends State<_CalendarView> _allDayNotifier.value = null; } + if (_hoveringDate != null) { + _hoveringDate = null; + } + _appointmentHoverNotifier.value = Offset(xPosition, yPosition); } @@ -2121,6 +2099,7 @@ class _CalendarViewState extends State<_CalendarView> } if (_calendarCellNotifier.value != null) { + _hoveringDate = null; _calendarCellNotifier.value = null; } @@ -2128,6 +2107,10 @@ class _CalendarViewState extends State<_CalendarView> _appointmentHoverNotifier.value = null; } + if (_hoveringDate != null) { + _hoveringDate = null; + } + _allDayNotifier.value = Offset(xPosition, yPosition); } @@ -2154,6 +2137,7 @@ class _CalendarViewState extends State<_CalendarView> } if (_calendarCellNotifier.value != null) { + _hoveringDate = null; _calendarCellNotifier.value = null; } @@ -2246,6 +2230,10 @@ class _CalendarViewState extends State<_CalendarView> } void _updatePointerHover(Offset globalPosition) { + if (widget.isMobilePlatform) { + return; + } + final RenderBox box = context.findRenderObject(); final Offset localPosition = box.globalToLocal(globalPosition); double viewHeaderHeight = @@ -2278,16 +2266,17 @@ class _CalendarViewState extends State<_CalendarView> if (localPosition.dy < viewHeaderHeight) { if (widget.view == CalendarView.day) { - xPosition = - _isRTL ? widget.width - localPosition.dx : localPosition.dx; - if ((_isRTL && xPosition < widget.width - timeLabelWidth) || - (!_isRTL && xPosition > timeLabelWidth)) { - _updateHoveringForAllDayPanel(xPosition, localPosition.dy); + if ((_isRTL && localPosition.dx < widget.width - timeLabelWidth) || + (!_isRTL && localPosition.dx > timeLabelWidth)) { + _updateHoveringForAllDayPanel(localPosition.dx, localPosition.dy); return; } _updateHoveringForViewHeader( - localPosition, xPosition, localPosition.dy, viewHeaderHeight); + localPosition, + _isRTL ? widget.width - localPosition.dx : localPosition.dx, + localPosition.dy, + viewHeaderHeight); return; } @@ -2304,27 +2293,31 @@ class _CalendarViewState extends State<_CalendarView> final double allDayExpanderHeight = panelHeight * _allDayExpanderAnimation.value; - final double allDayBottom = (widget.view == CalendarView.day) - ? viewHeaderHeight + allDayExpanderHeight + final double allDayBottom = widget.view == CalendarView.day + ? viewHeaderHeight : viewHeaderHeight + _allDayHeight + allDayExpanderHeight; if (localPosition.dy > viewHeaderHeight && - localPosition.dy < allDayBottom && - ((_isRTL && xPosition < widget.width - timeLabelWidth) || - (!_isRTL && xPosition > timeLabelWidth))) { - _updateHoveringForAllDayPanel( - xPosition, localPosition.dy - viewHeaderHeight); + localPosition.dy < allDayBottom) { + if ((_isRTL && localPosition.dx < widget.width - timeLabelWidth) || + (!_isRTL && localPosition.dx > timeLabelWidth)) { + _updateHoveringForAllDayPanel( + localPosition.dx, localPosition.dy - viewHeaderHeight); + } else { + _removeAllWidgetHovering(); + } + return; } yPosition = localPosition.dy - (viewHeaderHeight + allDayHeight); - final _AppointmentView appointment = _getAppointmentOnPoint( - _appointmentPainter._appointmentCollection, - localPosition.dx, - yPosition + _scrollController.offset); + final _AppointmentView appointment = _appointmentLayoutKey.currentState + ._getAppointmentViewOnPoint( + localPosition.dx, yPosition + _scrollController.offset); if (appointment != null) { _updateHoveringForAppointment( localPosition.dx, yPosition + _scrollController.offset); + _hoveringDate = null; return; } } else { @@ -2357,8 +2350,8 @@ class _CalendarViewState extends State<_CalendarView> yPosition += _timelineViewVerticalScrollController.offset; } - final _AppointmentView appointment = _getAppointmentOnPoint( - _appointmentPainter._appointmentCollection, xPosition, yPosition); + final _AppointmentView appointment = _appointmentLayoutKey.currentState + ._getAppointmentViewOnPoint(xPosition, yPosition); if (appointment != null) { _updateHoveringForAppointment(xPosition, yPosition); _hoveringDate = null; @@ -2534,7 +2527,7 @@ class _CalendarViewState extends State<_CalendarView> _allDayNotifier.value = null; } - _AppointmentView _getAppointmentOnPoint( + _AppointmentView _getAllDayAppointmentOnPoint( List<_AppointmentView> appointmentCollection, double x, double y) { if (appointmentCollection == null) { return null; @@ -2554,28 +2547,6 @@ class _CalendarViewState extends State<_CalendarView> } } - if (selectedAppointmentView == null && - widget.view == CalendarView.month && - _appointmentPainter._monthAppointmentCountViews != null && - widget.calendar.monthViewSettings.appointmentDisplayMode == - MonthAppointmentDisplayMode.appointment) { - for (int i = 0; - i < _appointmentPainter._monthAppointmentCountViews.length; - i++) { - final RRect rect = _appointmentPainter._monthAppointmentCountViews[i]; - if (rect != null && - rect.left <= x && - rect.right >= x && - rect.top <= y && - rect.bottom >= y) { - selectedAppointmentView = _AppointmentView() - ..appointment = Appointment() - ..appointmentRect = rect; - break; - } - } - } - return selectedAppointmentView; } @@ -2872,3 +2843,19 @@ class _CalendarViewState extends State<_CalendarView> ]); } } + +class _CustomNeverScrollableScrollPhysics extends NeverScrollableScrollPhysics { + /// Creates scroll physics that does not let the user scroll. + const _CustomNeverScrollableScrollPhysics({ScrollPhysics parent}) + : super(parent: parent); + + @override + _CustomNeverScrollableScrollPhysics applyTo(ScrollPhysics ancestor) { + /// Set the clamping scroll physics as default parent for never scroll + /// physics, because flutter framework set different parent physics + /// based on platform(iOS, Android, etc.,) + return _CustomNeverScrollableScrollPhysics( + parent: buildParent( + ClampingScrollPhysics(parent: RangeMaintainingScrollPhysics()))); + } +} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/custom_calendar_button.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/custom_calendar_button.dart new file mode 100644 index 000000000..e5cc216f8 --- /dev/null +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/custom_calendar_button.dart @@ -0,0 +1,239 @@ +part of calendar; + +/// Specifies the unconfirmed ripple animation duration used on custom splash. +/// The duration was unconfirmed because the ripple animation duration changed +/// based on its radius value. +const Duration _kUnconfirmedRippleSplashDuration = Duration(seconds: 1); + +/// Specifies the fade animation duration used on custom splash. +const Duration _kSplashFadeDuration = Duration(milliseconds: 500); + +/// Used to create the custom splash factory that shows the splash for inkwell +/// interaction. +class _CustomSplashFactory extends InteractiveInkFeatureFactory { + /// Called when the inkwell pressed and it return custom splash. + @override + InteractiveInkFeature create({ + @required MaterialInkController controller, + @required RenderBox referenceBox, + @required Offset position, + @required Color color, + @required TextDirection textDirection, + bool containedInkWell = false, + RectCallback rectCallback, + BorderRadius borderRadius, + ShapeBorder customBorder, + double radius, + VoidCallback onRemoved, + }) { + return _CustomSplash( + controller: controller, + referenceBox: referenceBox, + position: position, + color: color, + containedInkWell: containedInkWell, + borderRadius: borderRadius, + rectCallback: rectCallback, + onRemoved: onRemoved, + ); + } +} + +/// Custom ink splash used to animate the inkwell on intercation. +class _CustomSplash extends InteractiveInkFeature { + /// Begin a splash, centered at position relative to [referenceBox]. + /// + /// The [controller] argument is typically obtained via + /// `Material.of(context)`. + /// + /// If `containedInkWell` is true, then the splash will be sized to fit + /// the well rectangle, then clipped to it when drawn. The well + /// rectangle is the box returned by `rectCallback`, if provided, or + /// otherwise is the bounds of the [referenceBox]. + /// + /// If `containedInkWell` is false, then `rectCallback` should be null. + /// The ink splash is clipped only to the edges of the [Material]. + /// This is the default. + /// + /// When the splash is removed, `onRemoved` will be called. + _CustomSplash({ + @required MaterialInkController controller, + @required RenderBox referenceBox, + Offset position, + Color color, + bool containedInkWell = false, + RectCallback rectCallback, + BorderRadius borderRadius, + VoidCallback onRemoved, + }) : _position = position, + _borderRadius = borderRadius ?? BorderRadius.zero, + _targetRadius = _getTargetRadius( + referenceBox, containedInkWell, rectCallback, position), + _clipCallback = + _getClipCallback(referenceBox, containedInkWell, rectCallback), + _repositionToReferenceBox = !containedInkWell, + super( + controller: controller, + referenceBox: referenceBox, + color: color, + onRemoved: onRemoved) { + _radiusController = AnimationController( + duration: _kUnconfirmedRippleSplashDuration, vsync: controller.vsync) + ..addListener(controller.markNeedsPaint) + ..forward(); + _radius = _radiusController.drive(Tween( + begin: 0.0, + end: _targetRadius, + )); + _alphaController = AnimationController( + duration: _kSplashFadeDuration, vsync: controller.vsync) + ..addListener(controller.markNeedsPaint) + ..addStatusListener(_handleAlphaStatusChanged); + _alpha = _alphaController.drive(IntTween( + begin: color.alpha, + end: 0, + )); + + controller.addInkFeature(this); + } + + /// Position holds the input touch point. + final Offset _position; + + /// Specifies the border radius used on the inkwell + final BorderRadius _borderRadius; + + /// Radius of ink circle to be drawn on canvas based on its position. + final double _targetRadius; + + /// clipCallback is the callback used to obtain the rect used for clipping + /// the ink effect. If it is null, no clipping is performed on the ink circle. + final RectCallback _clipCallback; + + /// Specifies the reference box repositioned or not. Its value depends on + /// contained inkwell property. + final bool _repositionToReferenceBox; + + /// Animation used to show a ripple. + Animation _radius; + + /// Controller used to handle the ripple animation. + AnimationController _radiusController; + + /// Animation used to handle a opacity. + Animation _alpha; + + /// Controller used to handle the opacity animation. + AnimationController _alphaController; + + @override + void confirm() { + /// Calculate the ripple animation duration from its radius value and start + /// the animation. + Duration duration = Duration(milliseconds: (_targetRadius * 10).floor()); + duration = duration > _kUnconfirmedRippleSplashDuration + ? _kUnconfirmedRippleSplashDuration + : duration; + _radiusController + ..duration = duration + ..forward(); + _alphaController.forward(); + } + + @override + void cancel() { + _alphaController?.forward(); + } + + void _handleAlphaStatusChanged(AnimationStatus status) { + /// Dispose inkwell animation when the animation completed. + if (status == AnimationStatus.completed) dispose(); + } + + @override + void dispose() { + _radiusController.dispose(); + _alphaController.dispose(); + _radiusController = null; + _alphaController = null; + super.dispose(); + } + + ///Draws an ink splash or ink ripple on the canvas. + @override + void paintFeature(Canvas canvas, Matrix4 transform) { + final Paint paint = Paint()..color = color.withAlpha(_alpha.value); + Offset center = _position; + + /// If the reference box needs to reposition then its 'rectCallback' value + /// is null, so calculate the position based on reference box. + if (_repositionToReferenceBox) { + center = Offset.lerp(center, referenceBox.size.center(Offset.zero), + _radiusController.value); + } + + /// Get the offset needs to translate, if it not specified then it + /// returns null value. + final Offset originOffset = MatrixUtils.getAsTranslation(transform); + canvas.save(); + + /// Translate the canvas based on offset value. + if (originOffset == null) { + canvas.transform(transform.storage); + } else { + canvas.translate(originOffset.dx, originOffset.dy); + } + + if (_clipCallback != null) { + /// Clip and draw the rect with fade animation value on canvas. + final Rect rect = _clipCallback(); + if (_borderRadius != BorderRadius.zero) { + final RRect roundedRect = RRect.fromRectAndCorners( + rect, + topLeft: _borderRadius.topLeft, + topRight: _borderRadius.topRight, + bottomLeft: _borderRadius.bottomLeft, + bottomRight: _borderRadius.bottomRight, + ); + canvas.clipRRect(roundedRect); + canvas.drawRRect(roundedRect, paint); + } else { + canvas.clipRect(rect); + canvas.drawRect(rect, paint); + } + } + + /// Draw the ripple on canvas. + canvas.drawCircle(center, _radius.value, paint); + canvas.restore(); + } +} + +/// Returns the maximum radius value calculated based on input touch position. +double _getTargetRadius(RenderBox referenceBox, bool containedInkWell, + RectCallback rectCallback, Offset position) { + /// If `containedInkWell` is false, then `rectCallback` should be null. + if (!containedInkWell) { + return Material.defaultSplashRadius; + } + + final Size size = + rectCallback != null ? rectCallback().size : referenceBox.size; + final double d1 = (position - size.topLeft(Offset.zero)).distance; + final double d2 = (position - size.topRight(Offset.zero)).distance; + final double d3 = (position - size.bottomLeft(Offset.zero)).distance; + final double d4 = (position - size.bottomRight(Offset.zero)).distance; + return math.max(math.max(d1, d2), math.max(d3, d4)).ceilToDouble(); +} + +/// Return the rect callback value based on its argument value. +RectCallback _getClipCallback( + RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback) { + if (rectCallback != null) { + /// If `containedInkWell` is false, then `rectCallback` should be null. + assert(containedInkWell); + return rectCallback; + } + if (containedInkWell) return () => Offset.zero & referenceBox.size; + return null; +} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/day_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/day_view.dart index fca5e3b58..2adeab933 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/day_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/day_view.dart @@ -1,7 +1,15 @@ part of calendar; -class _TimeSlotView extends CustomPainter { - _TimeSlotView( +class _TimeRegionView { + _TimeRegionView({this.visibleIndex, this.region, this.bound}); + + int visibleIndex = -1; + TimeRegion region; + Rect bound; +} + +class _TimeSlotWidget extends StatefulWidget { + _TimeSlotWidget( this.visibleDates, this.horizontalLinesCount, this.timeIntervalHeight, @@ -12,8 +20,10 @@ class _TimeSlotView extends CustomPainter { this.isRTL, this.specialRegion, this.calendarCellNotifier, - this.textScaleFactor) - : super(repaint: calendarCellNotifier); + this.textScaleFactor, + this.timeRegionBuilder, + this.width, + this.height); final List visibleDates; final double horizontalLinesCount; @@ -26,98 +36,101 @@ class _TimeSlotView extends CustomPainter { final ValueNotifier calendarCellNotifier; final List specialRegion; final double textScaleFactor; - double _cellWidth; - Paint _linePainter; + final TimeRegionBuilder timeRegionBuilder; + final double width; + final double height; @override - void paint(Canvas canvas, Size size) { - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - final double width = size.width - timeLabelWidth; - _cellWidth = width / visibleDates.length; - _linePainter = _linePainter ?? Paint(); + _TimeSlotWidgetState createState() => _TimeSlotWidgetState(); +} - if (specialRegion != null) { - _addSpecialRegions(canvas, size); - } +class _TimeSlotWidgetState extends State<_TimeSlotWidget> { + List _children; + List<_TimeRegionView> _specialRegionViews; - double x, y; - y = timeIntervalHeight; - _linePainter.style = PaintingStyle.stroke; - _linePainter.strokeWidth = 0.5; - _linePainter.strokeCap = StrokeCap.round; - _linePainter.color = cellBorderColor ?? calendarTheme.cellBorderColor; - - for (int i = 1; i <= horizontalLinesCount; i++) { - final Offset start = Offset(isRTL ? 0 : timeLabelWidth, y); - final Offset end = - Offset(isRTL ? size.width - timeLabelWidth : size.width, y); - canvas.drawLine(start, end, _linePainter); + @override + void initState() { + _children = []; + _updateSpecialRegionDetails(); + super.initState(); + } - y += timeIntervalHeight; - if (y == size.height) { - break; - } + @override + void didUpdateWidget(_TimeSlotWidget oldWidget) { + if (widget.visibleDates != oldWidget.visibleDates || + widget.horizontalLinesCount != oldWidget.horizontalLinesCount || + widget.timeIntervalHeight != oldWidget.timeIntervalHeight || + widget.timeLabelWidth != oldWidget.timeLabelWidth || + widget.isRTL != oldWidget.isRTL || + widget.timeSlotViewSettings != oldWidget.timeSlotViewSettings || + widget.width != oldWidget.width || + widget.height != oldWidget.height || + widget.timeRegionBuilder != oldWidget.timeRegionBuilder || + !_isCollectionEqual(widget.specialRegion, oldWidget.specialRegion)) { + _updateSpecialRegionDetails(); + _children.clear(); } - if (isRTL) { - x = _cellWidth; - } else { - x = timeLabelWidth + _cellWidth; - } + super.didUpdateWidget(oldWidget); + } - for (int i = 0; i < visibleDates.length - 1; i++) { - final Offset start = Offset(x, 0); - final Offset end = Offset(x, size.height); - canvas.drawLine(start, end, _linePainter); - x += _cellWidth; + @override + Widget build(BuildContext context) { + _children ??= []; + if (_children.isEmpty && + widget.timeRegionBuilder != null && + _specialRegionViews != null && + _specialRegionViews.isNotEmpty) { + final int count = _specialRegionViews.length; + for (int i = 0; i < count; i++) { + final _TimeRegionView view = _specialRegionViews[i]; + final Widget child = widget.timeRegionBuilder( + context, + TimeRegionDetails( + region: view.region, + date: widget.visibleDates[view.visibleIndex], + bounds: view.bound)); + + /// Throw exception when builder return widget is null. + assert(child != null, 'Widget must not be null'); + _children.add(RepaintBoundary(child: child)); + } } - if (calendarCellNotifier.value != null) { - _addMouseHoveringForTimeSlot(canvas, size); - } + return _TimeSlotRenderWidget( + widget.visibleDates, + widget.horizontalLinesCount, + widget.timeIntervalHeight, + widget.timeLabelWidth, + widget.cellBorderColor, + widget.calendarTheme, + widget.timeSlotViewSettings, + widget.isRTL, + widget.specialRegion, + widget.calendarCellNotifier, + widget.textScaleFactor, + widget.width, + widget.height, + _specialRegionViews, + widgets: _children, + ); } - void _addMouseHoveringForTimeSlot(Canvas canvas, Size size) { - final double padding = 0.5; - double left = (calendarCellNotifier.value.dx ~/ _cellWidth) * _cellWidth; - double top = (calendarCellNotifier.value.dy ~/ timeIntervalHeight) * - timeIntervalHeight; - _linePainter.style = PaintingStyle.stroke; - _linePainter.strokeWidth = 2; - _linePainter.color = calendarTheme.selectionBorderColor.withOpacity(0.4); - left += (isRTL ? 0 : timeLabelWidth); - top = top == 0 ? top + padding : top; - double height = timeIntervalHeight; - if (top == padding) { - height -= padding; + void _updateSpecialRegionDetails() { + _specialRegionViews = <_TimeRegionView>[]; + if (widget.specialRegion == null || widget.specialRegion.isEmpty) { + return; } - canvas.drawRect( - Rect.fromLTWH( - left, - top, - left + _cellWidth == size.width ? _cellWidth - padding : _cellWidth, - top + height == size.height ? height - padding : height), - _linePainter); - } - - void _addSpecialRegions(Canvas canvas, Size size) { - final double minuteHeight = - timeIntervalHeight / _getTimeInterval(timeSlotViewSettings); - final DateTime startDate = _convertToStartTime(visibleDates[0]); + final double minuteHeight = widget.timeIntervalHeight / + _getTimeInterval(widget.timeSlotViewSettings); + final DateTime startDate = _convertToStartTime(widget.visibleDates[0]); final DateTime endDate = - _convertToEndTime(visibleDates[visibleDates.length - 1]); - final TextPainter painter = TextPainter( - textDirection: TextDirection.ltr, - maxLines: 1, - textAlign: isRTL ? TextAlign.right : TextAlign.left, - textScaleFactor: textScaleFactor, - textWidthBasis: TextWidthBasis.longestLine); - - _linePainter.style = PaintingStyle.fill; - for (int i = 0; i < specialRegion.length; i++) { - final TimeRegion region = specialRegion[i]; - _linePainter.color = region.color ?? Colors.grey.withOpacity(0.2); + _convertToEndTime(widget.visibleDates[widget.visibleDates.length - 1]); + final double width = widget.width - widget.timeLabelWidth; + final double cellWidth = width / widget.visibleDates.length; + for (int i = 0; i < widget.specialRegion.length; i++) { + final TimeRegion region = widget.specialRegion[i]; final DateTime regionStartTime = region._actualStartTime; final DateTime regionEndTime = region._actualEndTime; @@ -136,13 +149,14 @@ class _TimeSlotView extends CustomPainter { continue; } - int startIndex = _getVisibleDateIndex(visibleDates, regionStartTime); - int endIndex = _getVisibleDateIndex(visibleDates, regionEndTime); + int startIndex = + _getVisibleDateIndex(widget.visibleDates, regionStartTime); + int endIndex = _getVisibleDateIndex(widget.visibleDates, regionEndTime); double startYPosition = _getTimeToPosition( Duration( hours: regionStartTime.hour, minutes: regionStartTime.minute), - timeSlotViewSettings, + widget.timeSlotViewSettings, minuteHeight); if (startIndex == -1) { if (startDate.isAfter(regionStartTime)) { @@ -151,8 +165,8 @@ class _TimeSlotView extends CustomPainter { startIndex = 0; } else { /// Find the next index when the start date as non working date. - for (int k = 1; k < visibleDates.length; k++) { - final DateTime currentDate = visibleDates[k]; + for (int k = 1; k < widget.visibleDates.length; k++) { + final DateTime currentDate = widget.visibleDates[k]; if (currentDate.isBefore(regionStartTime)) { continue; } @@ -173,13 +187,13 @@ class _TimeSlotView extends CustomPainter { double endYPosition = _getTimeToPosition( Duration(hours: regionEndTime.hour, minutes: regionEndTime.minute), - timeSlotViewSettings, + 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 = visibleDates.length - 2; k >= 0; k--) { - final DateTime currentDate = visibleDates[k]; + for (int k = widget.visibleDates.length - 2; k >= 0; k--) { + final DateTime currentDate = widget.visibleDates[k]; if (currentDate.isAfter(regionEndTime)) { continue; } @@ -189,82 +203,518 @@ class _TimeSlotView extends CustomPainter { } if (endIndex == -1) { - endIndex = visibleDates.length - 1; + endIndex = widget.visibleDates.length - 1; } } else { /// Set index as visible date end date index when the /// region end date before the visible end date - endIndex = visibleDates.length - 1; + endIndex = widget.visibleDates.length - 1; } /// End date as non working day and its index as previous date index. /// so assign the position value as view height - endYPosition = size.height; + endYPosition = widget.height; } - final TextStyle textStyle = region.textStyle ?? - TextStyle( - color: calendarTheme.brightness != null && - calendarTheme.brightness == Brightness.dark - ? Colors.white54 - : Colors.black45); for (int j = startIndex; j <= endIndex; j++) { final double startPosition = j == startIndex ? startYPosition : 0; - final double endPosition = j == endIndex ? endYPosition : size.height; + final double endPosition = j == endIndex ? endYPosition : widget.height; /// Check the start and end position not between the visible hours /// position(not between start and end hour) if ((startPosition <= 0 && endPosition <= 0) || - (startPosition >= size.height && endPosition >= size.height) || + (startPosition >= widget.height && endPosition >= widget.height) || (startPosition == endPosition)) { continue; } - double startXPosition = timeLabelWidth + (j * _cellWidth); - if (isRTL) { - startXPosition = size.width - (startXPosition + _cellWidth); + double startXPosition = widget.timeLabelWidth + (j * cellWidth); + if (widget.isRTL) { + startXPosition = widget.width - (startXPosition + cellWidth); } final Rect rect = Rect.fromLTRB(startXPosition, startPosition, - startXPosition + _cellWidth, endPosition); - canvas.drawRect(rect, _linePainter); - if ((region.text == null || region.text.isEmpty) && - region.iconData == null) { + startXPosition + cellWidth, endPosition); + _specialRegionViews + .add(_TimeRegionView(region: region, visibleIndex: j, bound: rect)); + } + } + } +} + +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, + {List widgets}) + : super(children: widgets); + + final List visibleDates; + final double horizontalLinesCount; + final double timeIntervalHeight; + final double timeLabelWidth; + final Color cellBorderColor; + final SfCalendarThemeData calendarTheme; + final TimeSlotViewSettings timeSlotViewSettings; + final bool isRTL; + final ValueNotifier calendarCellNotifier; + final List specialRegion; + final double textScaleFactor; + final double width; + final double height; + final List<_TimeRegionView> specialRegionBounds; + + @override + _TimeSlotRenderObject createRenderObject(BuildContext context) { + return _TimeSlotRenderObject( + visibleDates, + horizontalLinesCount, + timeIntervalHeight, + timeLabelWidth, + cellBorderColor, + calendarTheme, + timeSlotViewSettings, + isRTL, + specialRegion, + calendarCellNotifier, + textScaleFactor, + width, + height, + specialRegionBounds); + } + + @override + void updateRenderObject( + BuildContext context, _TimeSlotRenderObject renderObject) { + renderObject + ..visibleDates = visibleDates + ..horizontalLinesCount = horizontalLinesCount + ..timeIntervalHeight = timeIntervalHeight + ..timeLabelWidth = timeLabelWidth + ..cellBorderColor = cellBorderColor + ..calendarTheme = calendarTheme + ..timeSlotViewSettings = timeSlotViewSettings + ..isRTL = isRTL + ..specialRegion = specialRegion + ..calendarCellNotifier = calendarCellNotifier + ..textScaleFactor = textScaleFactor + ..width = width + ..height = height + ..specialRegionBounds = specialRegionBounds; + } +} + +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); + + List _visibleDates; + + List get visibleDates => _visibleDates; + + set visibleDates(List value) { + if (_visibleDates == value) { + return; + } + + _visibleDates = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + double _horizontalLinesCount; + + double get horizontalLinesCount => _horizontalLinesCount; + + set horizontalLinesCount(double value) { + if (_horizontalLinesCount == value) { + return; + } + + _horizontalLinesCount = value; + markNeedsPaint(); + } + + double _timeIntervalHeight; + + double get timeIntervalHeight => _timeIntervalHeight; + + set timeIntervalHeight(double value) { + if (_timeIntervalHeight == value) { + return; + } + + _timeIntervalHeight = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + double _timeLabelWidth; + + double get timeLabelWidth => _timeLabelWidth; + + set timeLabelWidth(double value) { + if (_timeLabelWidth == value) { + return; + } + + _timeLabelWidth = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + Color _cellBorderColor; + + Color get cellBorderColor => _cellBorderColor; + + set cellBorderColor(Color value) { + if (_cellBorderColor == value) { + return; + } + + _cellBorderColor = value; + markNeedsPaint(); + } + + SfCalendarThemeData _calendarTheme; + + SfCalendarThemeData get calendarTheme => _calendarTheme; + + set calendarTheme(SfCalendarThemeData value) { + if (_calendarTheme == value) { + return; + } + + _calendarTheme = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + TimeSlotViewSettings _timeSlotViewSettings; + + TimeSlotViewSettings get timeSlotViewSettings => _timeSlotViewSettings; + + set timeSlotViewSettings(TimeSlotViewSettings value) { + if (_timeSlotViewSettings == value) { + return; + } + + _timeSlotViewSettings = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + bool _isRTL; + + bool get isRTL => _isRTL; + + set isRTL(bool value) { + if (_isRTL == value) { + return; + } + + _isRTL = value; + markNeedsPaint(); + } + + ValueNotifier _calendarCellNotifier; + + ValueNotifier get calendarCellNotifier => _calendarCellNotifier; + + set calendarCellNotifier(ValueNotifier value) { + if (_calendarCellNotifier == value) { + return; + } + + _calendarCellNotifier?.removeListener(markNeedsPaint); + _calendarCellNotifier = value; + _calendarCellNotifier?.addListener(markNeedsPaint); + } + + double _width; + + double get width => _width; + + set width(double value) { + if (_width == value) { + return; + } + + _width = value; + markNeedsLayout(); + } + + double _height; + + double get height => _height; + + set height(double value) { + if (_height == value) { + return; + } + + _height = value; + markNeedsLayout(); + } + + double _textScaleFactor; + + double get textScaleFactor => _textScaleFactor; + + set textScaleFactor(double value) { + if (_textScaleFactor == value) { + return; + } + + _textScaleFactor = value; + markNeedsPaint(); + } + + List _specialRegion; + + List get specialRegion => _specialRegion; + + set specialRegion(List value) { + if (_isCollectionEqual(_specialRegion, value)) { + return; + } + + _specialRegion = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + List<_TimeRegionView> specialRegionBounds; + double _cellWidth; + Paint _linePainter; + + @override + bool get isRepaintBoundary => true; + + /// attach will called when the render object rendered in view. + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _calendarCellNotifier?.addListener(markNeedsPaint); + } + + /// detach will called when the render object removed from view. + @override + void detach() { + _calendarCellNotifier?.removeListener(markNeedsPaint); + super.detach(); + } + + @override + void performLayout() { + final Size widgetSize = constraints.biggest; + size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height); + RenderBox child = firstChild; + if (specialRegion == null || specialRegion.isEmpty) { + return; + } + + final int count = specialRegionBounds.length; + for (int i = 0; i < count; i++) { + final _TimeRegionView view = specialRegionBounds[i]; + if (child == null) { + continue; + } + final Rect rect = view.bound; + child.layout(constraints.copyWith( + minHeight: rect.height, + maxHeight: rect.height, + minWidth: rect.width, + maxWidth: rect.width)); + child = childAfter(child); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + RenderBox child = firstChild; + final bool isNeedDefaultPaint = childCount == 0; + final double width = size.width - timeLabelWidth; + _cellWidth = width / visibleDates.length; + _linePainter = _linePainter ?? Paint(); + if (isNeedDefaultPaint) { + _addSpecialRegions(context.canvas); + } else { + if (specialRegion == null || specialRegion.isEmpty) { + return; + } + + final int count = specialRegionBounds.length; + for (int i = 0; i < count; i++) { + final _TimeRegionView view = specialRegionBounds[i]; + if (child == null) { continue; } + final Rect rect = view.bound; + child.paint(context, Offset(rect.left, rect.top)); + child = childAfter(child); + } + } - painter.textDirection = TextDirection.ltr; - painter.textAlign = isRTL ? TextAlign.right : TextAlign.left; - if (region.iconData == null) { - painter.text = TextSpan(text: region.text, style: textStyle); - painter.ellipsis = '..'; - } else { - painter.text = TextSpan( - text: String.fromCharCode(region.iconData.codePoint), - style: - textStyle.copyWith(fontFamily: region.iconData.fontFamily)); - } + _drawTimeSlots(context.canvas); + } - painter.layout(minWidth: 0, maxWidth: rect.width - 4); - painter.paint(canvas, Offset(rect.left + 3, rect.top + 3)); + void _drawTimeSlots(Canvas canvas) { + double x, y; + y = timeIntervalHeight; + _linePainter.style = PaintingStyle.stroke; + _linePainter.strokeWidth = 0.5; + _linePainter.strokeCap = StrokeCap.round; + _linePainter.color = cellBorderColor ?? calendarTheme.cellBorderColor; + + for (int i = 1; i <= horizontalLinesCount; i++) { + final Offset start = Offset(isRTL ? 0 : timeLabelWidth, y); + final Offset end = + Offset(isRTL ? size.width - timeLabelWidth : size.width, y); + canvas.drawLine(start, end, _linePainter); + + y += timeIntervalHeight; + if (y == size.height) { + break; } } + + if (isRTL) { + x = _cellWidth; + } else { + x = timeLabelWidth + _cellWidth; + } + + for (int i = 0; i < visibleDates.length - 1; i++) { + final Offset start = Offset(x, 0); + final Offset end = Offset(x, size.height); + canvas.drawLine(start, end, _linePainter); + x += _cellWidth; + } + + if (calendarCellNotifier.value != null) { + _addMouseHoveringForTimeSlot(canvas, size); + } } - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _TimeSlotView oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.timeIntervalHeight != timeIntervalHeight || - oldWidget.timeLabelWidth != timeLabelWidth || - oldWidget.cellBorderColor != cellBorderColor || - oldWidget.horizontalLinesCount != horizontalLinesCount || - oldWidget.calendarTheme != calendarTheme || - oldWidget.specialRegion != specialRegion || - oldWidget.textScaleFactor != textScaleFactor || - oldWidget.isRTL != isRTL; + void _addMouseHoveringForTimeSlot(Canvas canvas, Size size) { + final double padding = 0.5; + double left = (calendarCellNotifier.value.dx ~/ _cellWidth) * _cellWidth; + double top = (calendarCellNotifier.value.dy ~/ timeIntervalHeight) * + timeIntervalHeight; + _linePainter.style = PaintingStyle.stroke; + _linePainter.strokeWidth = 2; + _linePainter.color = calendarTheme.selectionBorderColor.withOpacity(0.4); + left += (isRTL ? 0 : timeLabelWidth); + top = top == 0 ? top + padding : top; + double height = timeIntervalHeight; + if (top == padding) { + height -= padding; + } + + canvas.drawRect( + Rect.fromLTWH( + left, + top, + left + _cellWidth == size.width ? _cellWidth - padding : _cellWidth, + top + height == size.height ? height - padding : height), + _linePainter); + } + + void _addSpecialRegions(Canvas canvas) { + if (specialRegion == null || specialRegion.isEmpty) { + return; + } + + final TextPainter painter = TextPainter( + textDirection: TextDirection.ltr, + maxLines: 1, + textAlign: isRTL ? TextAlign.right : TextAlign.left, + textScaleFactor: textScaleFactor, + textWidthBasis: TextWidthBasis.longestLine); + + _linePainter.style = PaintingStyle.fill; + final int count = specialRegionBounds.length; + for (int i = 0; i < count; i++) { + final _TimeRegionView view = specialRegionBounds[i]; + final TimeRegion region = view.region; + _linePainter.color = region.color ?? Colors.grey.withOpacity(0.2); + + final TextStyle textStyle = region.textStyle ?? + TextStyle( + color: calendarTheme.brightness != null && + calendarTheme.brightness == Brightness.dark + ? Colors.white54 + : Colors.black45); + final Rect rect = view.bound; + canvas.drawRect(rect, _linePainter); + if ((region.text == null || region.text.isEmpty) && + region.iconData == null) { + continue; + } + + if (region.iconData == null) { + painter.text = TextSpan(text: region.text, style: textStyle); + painter.ellipsis = '..'; + } else { + painter.text = TextSpan( + text: String.fromCharCode(region.iconData.codePoint), + style: textStyle.copyWith(fontFamily: region.iconData.fontFamily)); + } + + painter.layout(minWidth: 0, maxWidth: rect.width - 4); + painter.paint(canvas, Offset(rect.left + 3, rect.top + 3)); + } } + @override + List Function(Size size) get semanticsBuilder => + _getSemanticsBuilder; + List _getSemanticsBuilder(Size size) { final List semanticsBuilder = []; @@ -315,21 +765,4 @@ class _TimeSlotView extends CustomPainter { return semanticsBuilder; } - - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the list - /// of custom painter semantics which contains the rect area and the semantics - /// properties for accessibility - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilder(size); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _TimeSlotView oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates; - } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/header_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/header_view.dart index 6281dc328..86ddf7992 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/header_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/header_view.dart @@ -1,185 +1,5 @@ part of calendar; -class _HeaderViewPainter extends CustomPainter { - _HeaderViewPainter( - this.headerStyle, - this.calendarTheme, - this.isRTL, - this.headerText, - this.showDatePickerButton, - this.isPickerShown, - this.mouseHoverPosition, - this.textScaleFactor); - - final CalendarHeaderStyle headerStyle; - final SfCalendarThemeData calendarTheme; - final bool isRTL; - final Offset mouseHoverPosition; - final bool showDatePickerButton; - final bool isPickerShown; - final double textScaleFactor; - final String headerText; - TextPainter _textPainter; - Paint _hoverPainter; - - @override - void paint(Canvas canvas, Size size) { - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - const double padding = 5; - double xPosition = 5.0; - _textPainter = _textPainter ?? TextPainter(); - _textPainter.textDirection = TextDirection.ltr; - _textPainter.maxLines = 1; - _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; - - final TextStyle style = - headerStyle.textStyle ?? calendarTheme.headerTextStyle; - - final TextSpan span = TextSpan(text: headerText, style: style); - _textPainter.text = span; - - if (headerStyle.textAlign == TextAlign.justify) { - _textPainter.textAlign = headerStyle.textAlign; - } - - _textPainter.layout(minWidth: 0, maxWidth: size.width - xPosition); - - final double pickerIconWidth = - showDatePickerButton ? style.fontSize ?? 14 : 0; - if (headerStyle.textAlign == TextAlign.right || - headerStyle.textAlign == TextAlign.end) { - xPosition = size.width - _textPainter.width - padding - pickerIconWidth; - } else if (headerStyle.textAlign == TextAlign.center) { - xPosition = size.width / 2 - _textPainter.width / 2 - pickerIconWidth / 2; - xPosition = xPosition < 0 ? 0 : xPosition; - } - - if (isRTL) { - xPosition = size.width - _textPainter.width - padding - pickerIconWidth; - if (headerStyle.textAlign == TextAlign.right || - headerStyle.textAlign == TextAlign.end) { - xPosition = 5.0; - } else if (headerStyle.textAlign == TextAlign.center) { - xPosition = - size.width / 2 - _textPainter.width / 2 - pickerIconWidth / 2; - xPosition = xPosition < 0 ? 0 : xPosition; - } - } - - if (mouseHoverPosition != null) { - _addMouseHovering(canvas, size, xPosition, padding, pickerIconWidth); - } - - if (kIsWeb && showDatePickerButton && isPickerShown) { - _hoverPainter ??= Paint(); - _hoverPainter.color = Colors.grey.withOpacity(0.3); - final double verticalPadding = 2; - canvas.drawRect( - Rect.fromLTWH( - xPosition - padding, - verticalPadding, - _textPainter.width + (padding * 2) + pickerIconWidth, - size.height - (verticalPadding * 2)), - _hoverPainter); - } - - xPosition = xPosition + (isRTL ? pickerIconWidth : 0); - _textPainter.paint( - canvas, Offset(xPosition, size.height / 2 - _textPainter.height / 2)); - if (isRTL) { - _drawDatePickerArrow(canvas, xPosition, style, size.width, size.height); - } else { - _drawDatePickerArrow(canvas, xPosition + _textPainter.width, style, - size.width, size.height); - } - } - - void _drawDatePickerArrow(Canvas canvas, double xPosition, TextStyle style, - double width, double height) { - if (!showDatePickerButton) { - return; - } - - Color arrowColor = style.color ?? Colors.black87; - arrowColor = arrowColor.withOpacity(arrowColor.opacity * 0.6); - final TextSpan span = TextSpan( - text: String.fromCharCode(isPickerShown - ? Icons.arrow_drop_up.codePoint - : Icons.arrow_drop_down.codePoint), - style: style.copyWith( - color: arrowColor, - fontFamily: 'MaterialIcons', - fontSize: style.fontSize ?? 14)); - _textPainter.text = span; - _textPainter.layout(minWidth: 0, maxWidth: width - xPosition); - _textPainter.paint( - canvas, - Offset(xPosition - (isRTL ? _textPainter.width : 0), - height / 2 - _textPainter.height / 2)); - } - - void _addMouseHovering(Canvas canvas, Size size, double xPosition, - double padding, double pickerIconWidth) { - _hoverPainter ??= Paint(); - final double verticalPadding = 2; - if (xPosition <= mouseHoverPosition.dx && - xPosition + _textPainter.width + pickerIconWidth >= - mouseHoverPosition.dx) { - _hoverPainter.color = (calendarTheme.brightness != null && - calendarTheme.brightness == Brightness.dark - ? Colors.white - : Colors.black87) - .withOpacity(0.04); - canvas.drawRect( - Rect.fromLTWH( - xPosition - padding, - verticalPadding, - _textPainter.width + (padding * 2) + pickerIconWidth, - size.height - (2 * verticalPadding)), - _hoverPainter); - } - } - - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the list - /// of custom painter semantics which contains the rect area and the semantics - /// properties for accessibility - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return [ - CustomPainterSemantics( - rect: Offset.zero & size, - properties: SemanticsProperties( - label: headerText, - textDirection: TextDirection.ltr, - ), - ), - ]; - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _HeaderViewPainter oldWidget = oldDelegate; - return oldWidget.headerText != headerText; - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _HeaderViewPainter oldWidget = oldDelegate; - return oldWidget.headerStyle != headerStyle || - oldWidget.calendarTheme != calendarTheme || - oldWidget.isRTL != isRTL || - oldWidget.showDatePickerButton != showDatePickerButton || - oldWidget.isPickerShown != isPickerShown || - oldWidget.headerText != headerText || - oldWidget.mouseHoverPosition != mouseHoverPosition || - oldWidget.textScaleFactor != textScaleFactor; - } -} - @immutable class _CalendarHeaderView extends StatefulWidget { const _CalendarHeaderView( @@ -210,7 +30,8 @@ class _CalendarHeaderView extends StatefulWidget { this.headerTapCallback, this.headerLongPressCallback, this.todayHighlightColor, - this.textScaleFactor); + this.textScaleFactor, + this.isMobilePlatform); final List visibleDates; final CalendarHeaderStyle headerStyle; @@ -240,13 +61,13 @@ class _CalendarHeaderView extends StatefulWidget { final bool isPickerShown; final double textScaleFactor; final Color todayHighlightColor; + final bool isMobilePlatform; @override _CalendarHeaderViewState createState() => _CalendarHeaderViewState(); } class _CalendarHeaderViewState extends State<_CalendarHeaderView> { - Offset _mouseHoverPosition; Map _calendarViews; @override @@ -269,7 +90,8 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { @override Widget build(BuildContext context) { - final bool isWebView = kIsWeb && widget.width > _kMobileViewWidth; + final bool useMobilePlatformUI = + _isMobileLayoutUI(widget.width, widget.isMobilePlatform); double arrowWidth = 0; double headerWidth = widget.width; @@ -319,7 +141,7 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { /// Today icon shown when the date picker enabled on calendar. if (widget.showDatePickerButton) { todayIconWidth = iconWidth; - if (isWebView) { + if (!useMobilePlatformUI) { /// 5 as padding for around today text view. final Size todayButtonSize = _getTextWidgetWidth( todayText, widget.height, widget.width - totalArrowWidth, context, @@ -330,11 +152,11 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { } double headerTextWidth = 0; - if (kIsWeb) { + if (!widget.isMobilePlatform) { final Size headerTextSize = _getTextWidgetWidth( headerString, widget.height, - widget.width - totalArrowWidth - todayIconWidth - 5, + widget.width - totalArrowWidth - todayIconWidth - padding, context, style: widget.headerStyle.textStyle ?? widget.calendarTheme.headerTextStyle); @@ -348,7 +170,26 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { if (isNeedViewSwitchOption) { calendarViewWidth = iconWidth; - if (isWebView) { + if (useMobilePlatformUI) { + maxHeaderHeight = + maxHeaderHeight != 0 && maxHeaderHeight <= widget.height + ? maxHeaderHeight + : widget.height; + + /// Render allowed views icon on mobile view. + calendarViewIcon = _getCalendarViewWidget( + useMobilePlatformUI, + false, + calendarViewWidth, + maxHeaderHeight, + style, + arrowColor, + headerTextColor, + widget.view, + widget.isMobilePlatform ? false : widget.viewChangeNotifier.value, + defaultCalendarViewTextSize, + semanticLabel: 'CalendarView'); + } else { /// Assign divider width when today icon text shown. dividerWidth = widget.showDatePickerButton ? 5 : 0; @@ -390,7 +231,7 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { for (int i = 0; i < allowedViewsLength; i++) { final CalendarView currentView = widget.allowedViews[i]; children.add(_getCalendarViewWidget( - isWebView, + useMobilePlatformUI, false, calendarViewsWidth[currentView], maxHeaderHeight, @@ -419,7 +260,7 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { calendarViewWidth = calendarViewSize.width + padding + headerIconTextWidth; children.add(_getCalendarViewWidget( - isWebView, + useMobilePlatformUI, true, calendarViewWidth, maxHeaderHeight, @@ -431,25 +272,6 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { defaultCalendarViewTextSize, semanticLabel: 'CalendarView')); } - } else { - maxHeaderHeight = - maxHeaderHeight != 0 && maxHeaderHeight <= widget.height - ? maxHeaderHeight - : widget.height; - - /// Render allowed views icon on mobile view. - calendarViewIcon = _getCalendarViewWidget( - isWebView, - false, - calendarViewWidth, - maxHeaderHeight, - style, - arrowColor, - headerTextColor, - widget.view, - kIsWeb ? widget.viewChangeNotifier.value : false, - defaultCalendarViewTextSize, - semanticLabel: 'CalendarView'); } } @@ -490,12 +312,26 @@ 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 = kIsWeb && + final bool isCenterAlignment = !widget.isMobilePlatform && (navigationArrowEnabled || isNeedViewSwitchOption) && widget.headerStyle.textAlign != null && (widget.headerStyle.textAlign == TextAlign.center || widget.headerStyle.textAlign == TextAlign.justify); - final Widget headerText = !kIsWeb + + Alignment _getHeaderAlignment() { + if (widget.headerStyle.textAlign == null || + widget.headerStyle.textAlign == TextAlign.left || + widget.headerStyle.textAlign == TextAlign.start) { + return widget.isRTL ? Alignment.centerRight : Alignment.centerLeft; + } else if (widget.headerStyle.textAlign == TextAlign.right || + widget.headerStyle.textAlign == TextAlign.end) { + return widget.isRTL ? Alignment.centerLeft : Alignment.centerRight; + } + + return Alignment.center; + } + + final Widget headerText = widget.isMobilePlatform ? Container( alignment: Alignment.center, color: widget.headerStyle.backgroundColor ?? @@ -503,122 +339,156 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { width: isCenterAlignment && headerWidth > 200 ? 200 : headerWidth, height: headerHeight, padding: const EdgeInsets.all(2), - child: FlatButton( - //// set splash color as transparent when header does not have - // date piker. - splashColor: - !widget.showDatePickerButton ? Colors.transparent : null, - highlightColor: - !widget.showDatePickerButton ? Colors.transparent : null, - hoverColor: - !widget.showDatePickerButton ? Colors.transparent : null, - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - onPressed: () { - widget.headerTapCallback( - calendarViewWidth + dividerWidth + todayIconWidth); - }, - onLongPress: () { - widget.headerLongPressCallback( - calendarViewWidth + dividerWidth + todayIconWidth); - }, - padding: const EdgeInsets.all(0), - child: Semantics( - label: headerString, - child: Container( - width: isCenterAlignment && headerWidth > 200 - ? 200 - : headerWidth, - height: headerHeight, - alignment: Alignment.centerLeft, - padding: EdgeInsets.symmetric(horizontal: 5), - child: Row( - mainAxisAlignment: _getAlignmentFromTextAlign(), - children: widget.showDatePickerButton - ? [ - Text( - headerString, - style: widget.headerStyle.textStyle ?? - widget.calendarTheme.headerTextStyle, - maxLines: 1, - ), - Icon( - widget.isPickerShown - ? Icons.arrow_drop_up - : Icons.arrow_drop_down, - color: arrowColor, - size: (widget.headerStyle.textStyle ?? - widget - .calendarTheme.headerTextStyle) - .fontSize ?? - 14, - ) - ] - : [ - Text( - headerString, - style: widget.headerStyle.textStyle ?? - widget.calendarTheme.headerTextStyle, - maxLines: 1, - ) - ], - )), - ), - ), + child: Material( + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + child: InkWell( + //// set splash color as transparent when header does not have + // date piker. + splashColor: + !widget.showDatePickerButton ? Colors.transparent : null, + highlightColor: + !widget.showDatePickerButton ? Colors.transparent : null, + hoverColor: + !widget.showDatePickerButton ? Colors.transparent : null, + splashFactory: _CustomSplashFactory(), + onTap: () { + widget.headerTapCallback( + calendarViewWidth + dividerWidth + todayIconWidth); + }, + onLongPress: () { + widget.headerLongPressCallback( + calendarViewWidth + dividerWidth + todayIconWidth); + }, + child: Semantics( + label: headerString, + child: Container( + width: isCenterAlignment && headerWidth > 200 + ? 200 + : headerWidth, + height: headerHeight, + alignment: Alignment.centerLeft, + padding: EdgeInsets.symmetric(horizontal: 5), + child: Row( + mainAxisAlignment: _getAlignmentFromTextAlign(), + children: widget.showDatePickerButton + ? [ + Flexible( + child: Text(headerString, + style: widget.headerStyle.textStyle ?? + widget.calendarTheme + .headerTextStyle, + maxLines: 1, + overflow: TextOverflow.clip, + softWrap: false, + textDirection: TextDirection.ltr)), + Icon( + widget.isPickerShown + ? Icons.arrow_drop_up + : Icons.arrow_drop_down, + color: arrowColor, + size: (widget.headerStyle.textStyle ?? + widget.calendarTheme + .headerTextStyle) + .fontSize ?? + 14, + ) + ] + : [ + Flexible( + child: Text(headerString, + style: widget.headerStyle.textStyle ?? + widget.calendarTheme + .headerTextStyle, + maxLines: 1, + overflow: TextOverflow.clip, + softWrap: false, + textDirection: TextDirection.ltr)) + ], + )), + ), + )), ) - : GestureDetector( - child: MouseRegion( - onEnter: (PointerEnterEvent event) { - _updateMouseHoveringPosition( - event.position, - arrowWidth, - isCenterAlignment && headerWidth > 200 - ? 200 - : headerWidth, - calendarViewWidth, - todayIconWidth, - dividerWidth); - }, - onHover: (PointerHoverEvent event) { - _updateMouseHoveringPosition( - event.position, - arrowWidth, - isCenterAlignment && headerWidth > 200 - ? 200 - : headerWidth, - calendarViewWidth, - todayIconWidth, - dividerWidth); - }, - onExit: (PointerExitEvent event) { - setState(() { - _mouseHoverPosition = null; - }); - }, - child: RepaintBoundary( - child: CustomPaint( - painter: _HeaderViewPainter( - widget.headerStyle, - widget.calendarTheme, - widget.isRTL, - headerString, - widget.showDatePickerButton, - widget.isPickerShown, - _mouseHoverPosition, - widget.textScaleFactor), - size: isCenterAlignment - ? Size( - headerWidth > 200 ? 200 : headerWidth, headerHeight) - : Size(headerWidth, headerHeight), - ))), - onTapUp: (TapUpDetails details) { - widget.headerTapCallback( - calendarViewWidth + dividerWidth + todayIconWidth); - }, - onLongPressStart: (LongPressStartDetails details) { - widget.headerLongPressCallback( - calendarViewWidth + dividerWidth + todayIconWidth); - }); + : Container( + alignment: _getHeaderAlignment(), + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + width: isCenterAlignment && headerWidth > 200 ? 200 : headerWidth, + height: headerHeight, + padding: const EdgeInsets.all(2), + child: Material( + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + child: InkWell( + //// set splash color as transparent when header does not have + // date piker. + splashColor: + !widget.showDatePickerButton ? Colors.transparent : null, + highlightColor: + !widget.showDatePickerButton ? Colors.transparent : null, + splashFactory: _CustomSplashFactory(), + onTap: () { + widget.headerTapCallback( + calendarViewWidth + dividerWidth + todayIconWidth); + }, + onLongPress: () { + widget.headerLongPressCallback( + calendarViewWidth + dividerWidth + todayIconWidth); + }, + child: Semantics( + label: headerString, + child: Container( + color: + widget.showDatePickerButton && widget.isPickerShown + ? Colors.grey.withOpacity(0.3) + : widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + width: isCenterAlignment && headerTextWidth > 200 + ? 200 + : headerTextWidth, + height: headerHeight, + alignment: Alignment.center, + padding: EdgeInsets.symmetric(horizontal: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: widget.showDatePickerButton + ? [ + Flexible( + child: Text(headerString, + style: widget.headerStyle.textStyle ?? + widget.calendarTheme + .headerTextStyle, + maxLines: 1, + overflow: TextOverflow.clip, + softWrap: false, + textDirection: TextDirection.ltr)), + Icon( + widget.isPickerShown + ? Icons.arrow_drop_up + : Icons.arrow_drop_down, + color: arrowColor, + size: (widget.headerStyle.textStyle ?? + widget.calendarTheme + .headerTextStyle) + .fontSize ?? + 14, + ) + ] + : [ + Flexible( + child: Text(headerString, + style: widget.headerStyle.textStyle ?? + widget.calendarTheme + .headerTextStyle, + maxLines: 1, + overflow: TextOverflow.clip, + softWrap: false, + textDirection: TextDirection.ltr)) + ], + )), + ), + )), + ); final Container leftArrow = Container( alignment: Alignment.center, @@ -627,32 +497,35 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { width: arrowWidth, height: headerHeight, padding: const EdgeInsets.all(2), - child: FlatButton( - //// set splash color as transparent when arrow reaches min date(disabled) - splashColor: prevArrowColor != arrowColor ? Colors.transparent : null, - highlightColor: - prevArrowColor != arrowColor ? Colors.transparent : null, - hoverColor: prevArrowColor != arrowColor ? Colors.transparent : null, - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - onPressed: _backward, - padding: const EdgeInsets.all(0), - 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, - )), - ), - ), + child: Material( + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + child: InkWell( + //// set splash color as transparent when arrow reaches min date(disabled) + splashColor: + prevArrowColor != arrowColor ? Colors.transparent : null, + highlightColor: + prevArrowColor != arrowColor ? Colors.transparent : null, + hoverColor: + prevArrowColor != arrowColor ? Colors.transparent : null, + splashFactory: _CustomSplashFactory(), + onTap: _backward, + child: Semantics( + label: 'Backward', + child: Container( + width: arrowWidth, + height: headerHeight, + alignment: Alignment.center, + child: Icon( + widget.navigationDirection == + MonthNavigationDirection.horizontal + ? Icons.chevron_left + : Icons.keyboard_arrow_up, + color: prevArrowColor, + size: arrowSize, + )), + ), + )), ); final Container rightArrow = Container( @@ -662,32 +535,35 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { width: arrowWidth, height: headerHeight, padding: const EdgeInsets.all(2), - child: FlatButton( - //// set splash color as transparent when arrow reaches max date(disabled) - splashColor: nextArrowColor != arrowColor ? Colors.transparent : null, - highlightColor: - nextArrowColor != arrowColor ? Colors.transparent : null, - hoverColor: nextArrowColor != arrowColor ? Colors.transparent : null, - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - onPressed: _forward, - padding: const EdgeInsets.all(0), - 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, - )), - ), - ), + child: Material( + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + child: InkWell( + //// set splash color as transparent when arrow reaches max date(disabled) + splashColor: + nextArrowColor != arrowColor ? Colors.transparent : null, + highlightColor: + nextArrowColor != arrowColor ? Colors.transparent : null, + hoverColor: + nextArrowColor != arrowColor ? Colors.transparent : null, + splashFactory: _CustomSplashFactory(), + onTap: _forward, + child: Semantics( + label: 'Forward', + child: Container( + width: arrowWidth, + height: headerHeight, + alignment: Alignment.center, + child: Icon( + widget.navigationDirection == + MonthNavigationDirection.horizontal + ? Icons.chevron_right + : Icons.keyboard_arrow_down, + color: nextArrowColor, + size: arrowSize, + )), + ), + )), ); final Widget todayIcon = Container( @@ -697,81 +573,84 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { width: todayIconWidth, height: headerHeight, padding: const EdgeInsets.all(2), - child: FlatButton( - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - onPressed: () { - widget.removePicker(); - widget.controller.displayDate = DateTime.now(); - }, - padding: const EdgeInsets.all(0), - child: Semantics( - label: todayText, - child: isWebView - ? Container( - width: todayIconWidth, - alignment: Alignment.center, - child: Text( - todayText, - style: TextStyle( - color: headerTextColor, - fontSize: defaultCalendarViewTextSize), - maxLines: 1, - )) - : Container( - width: todayIconWidth, - height: headerHeight, - alignment: Alignment.center, - child: Icon( - Icons.today, - color: style.color, - size: style.fontSize, - )), - ), - ), + child: Material( + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + child: InkWell( + splashFactory: _CustomSplashFactory(), + onTap: () { + widget.removePicker(); + widget.controller.displayDate = DateTime.now(); + }, + child: Semantics( + label: todayText, + child: useMobilePlatformUI + ? Container( + width: todayIconWidth, + height: headerHeight, + alignment: Alignment.center, + child: Icon( + Icons.today, + color: style.color, + size: style.fontSize, + )) + : Container( + width: todayIconWidth, + alignment: Alignment.center, + child: Text( + todayText, + style: TextStyle( + color: headerTextColor, + fontSize: defaultCalendarViewTextSize), + maxLines: 1, + textDirection: TextDirection.ltr, + )), + ), + )), ); - final Widget dividerWidget = - widget.showDatePickerButton && isNeedViewSwitchOption && isWebView - ? Container( - alignment: Alignment.center, - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - width: dividerWidth, - height: headerHeight, - padding: const EdgeInsets.symmetric(vertical: 5), - child: VerticalDivider( - color: Colors.grey, - thickness: 0.5, - )) - : Container( - width: 0, - height: 0, - ); + final Widget dividerWidget = widget.showDatePickerButton && + isNeedViewSwitchOption && + !useMobilePlatformUI + ? Container( + alignment: Alignment.center, + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + width: dividerWidth, + height: headerHeight, + padding: const EdgeInsets.symmetric(vertical: 5), + child: VerticalDivider( + color: Colors.grey, + thickness: 0.5, + )) + : Container( + width: 0, + height: 0, + ); List rowChildren = []; if (widget.headerStyle.textAlign == null || widget.headerStyle.textAlign == TextAlign.left || widget.headerStyle.textAlign == TextAlign.start) { - if (kIsWeb) { + if (widget.isMobilePlatform) { rowChildren = [ - leftArrow, - rightArrow, headerText, todayIcon, - dividerWidget, + calendarViewIcon, + leftArrow, + rightArrow, ]; - isWebView - ? rowChildren.addAll(children) - : rowChildren.add(calendarViewIcon); } else { rowChildren = [ - headerText, - todayIcon, - calendarViewIcon, leftArrow, rightArrow, + headerText, + todayIcon, + dividerWidget, ]; + useMobilePlatformUI + ? rowChildren.add(calendarViewIcon) + : rowChildren.addAll(children); } return Row( @@ -780,17 +659,7 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { children: rowChildren); } else if (widget.headerStyle.textAlign == TextAlign.right || widget.headerStyle.textAlign == TextAlign.end) { - if (kIsWeb) { - isWebView - ? rowChildren.addAll(children) - : rowChildren.add(calendarViewIcon); - - rowChildren.add(dividerWidget); - rowChildren.add(todayIcon); - rowChildren.add(headerText); - rowChildren.add(leftArrow); - rowChildren.add(rightArrow); - } else { + if (widget.isMobilePlatform) { rowChildren = [ leftArrow, rightArrow, @@ -798,6 +667,16 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { todayIcon, headerText, ]; + } else { + useMobilePlatformUI + ? rowChildren.add(calendarViewIcon) + : rowChildren.addAll(children); + + rowChildren.add(dividerWidget); + rowChildren.add(todayIcon); + rowChildren.add(headerText); + rowChildren.add(leftArrow); + rowChildren.add(rightArrow); } return Row( @@ -805,28 +684,26 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { crossAxisAlignment: CrossAxisAlignment.center, children: rowChildren); } else { - if (kIsWeb) { + if (widget.isMobilePlatform) { rowChildren = [ leftArrow, headerText, - rightArrow, todayIcon, dividerWidget, + calendarViewIcon, + rightArrow, ]; - isWebView - ? rowChildren.addAll(children) - : rowChildren.add(calendarViewIcon); } else { rowChildren = [ leftArrow, headerText, + rightArrow, todayIcon, dividerWidget, ]; - isWebView - ? rowChildren.addAll(children) - : rowChildren.add(calendarViewIcon); - rowChildren.add(rightArrow); + useMobilePlatformUI + ? rowChildren.add(calendarViewIcon) + : rowChildren.addAll(children); } return Row( @@ -857,7 +734,7 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { } Widget _getCalendarViewWidget( - bool isWebView, + bool useMobilePlatformUI, bool isNeedIcon, double width, double height, @@ -876,74 +753,79 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { width: width, height: height, padding: EdgeInsets.all(2), - child: FlatButton( - color: isHighlighted && (isNeedIcon || !isWebView) - ? Colors.grey.withOpacity(0.3) - : widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - onPressed: () { - if (isNeedIcon || !isWebView) { - widget.viewChangeNotifier.value = !widget.viewChangeNotifier.value; - } else { - widget.controller.view = view; - } - }, - padding: const EdgeInsets.all(0), - child: Semantics( - label: semanticLabel ?? text, - child: isWebView - ? (isNeedIcon + child: Material( + color: isHighlighted && (isNeedIcon || useMobilePlatformUI) + ? Colors.grey.withOpacity(0.3) + : widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + child: InkWell( + splashFactory: _CustomSplashFactory(), + onTap: () { + if (isNeedIcon || useMobilePlatformUI) { + widget.viewChangeNotifier.value = + !widget.viewChangeNotifier.value; + } else { + widget.controller.view = view; + } + }, + child: Semantics( + label: semanticLabel ?? text, + child: useMobilePlatformUI ? Container( width: width, height: height, alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( + child: Icon( + Icons.more_vert, + color: style.color, + size: style.fontSize, + )) + : (isNeedIcon + ? Container( + width: width, + height: height, + alignment: Alignment.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + text, + style: TextStyle( + color: headerTextColor, + fontSize: defaultCalendarViewTextSize), + maxLines: 1, + textDirection: TextDirection.ltr, + ), + Icon( + widget.viewChangeNotifier.value + ? Icons.arrow_drop_up + : Icons.arrow_drop_down, + color: arrowColor, + size: (widget.headerStyle.textStyle ?? + widget + .calendarTheme.headerTextStyle) + .fontSize ?? + 14, + ) + ], + )) + : Container( + width: width, + height: height, + alignment: Alignment.center, + child: Text( text, style: TextStyle( - color: headerTextColor, + color: isHighlighted + ? widget.todayHighlightColor ?? + widget.calendarTheme.todayHighlightColor + : headerTextColor, fontSize: defaultCalendarViewTextSize), maxLines: 1, - ), - Icon( - widget.viewChangeNotifier.value - ? Icons.arrow_drop_up - : Icons.arrow_drop_down, - color: arrowColor, - size: (widget.headerStyle.textStyle ?? - widget.calendarTheme.headerTextStyle) - .fontSize ?? - 14, - ) - ], - )) - : Container( - width: width, - height: height, - alignment: Alignment.center, - child: Text( - text, - style: TextStyle( - color: isHighlighted - ? widget.todayHighlightColor ?? - widget.calendarTheme.todayHighlightColor - : headerTextColor, - fontSize: defaultCalendarViewTextSize), - maxLines: 1, - ))) - : Container( - width: width, - height: height, - alignment: Alignment.center, - child: Icon( - Icons.more_vert, - color: style.color, - size: style.fontSize, - )), - ), - ), + textDirection: TextDirection.ltr, + ))), + ), + )), ); } @@ -1028,48 +910,4 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { return null; } - - void _updateMouseHoveringPosition( - Offset globalPosition, - double arrowWidth, - double headerWidth, - double calendarViewWidth, - double todayIconWidth, - double dividerWidth) { - final RenderBox box = context.findRenderObject(); - final Offset localPosition = box.globalToLocal(globalPosition); - double xPosition = localPosition.dx; - - if ((widget.headerStyle.textAlign == null || - widget.headerStyle.textAlign == TextAlign.left || - widget.headerStyle.textAlign == TextAlign.start)) { - xPosition = localPosition.dx - - (widget.isRTL - ? calendarViewWidth + todayIconWidth + dividerWidth - : arrowWidth * 2); - } else if (widget.headerStyle.textAlign == TextAlign.center || - widget.headerStyle.textAlign == TextAlign.justify) { - final double _headerStartPosition = ((widget.width - - headerWidth - - (arrowWidth * 2) - - calendarViewWidth - - dividerWidth - - todayIconWidth) / - 2); - xPosition = localPosition.dx - - _headerStartPosition - - (widget.isRTL - ? arrowWidth + calendarViewWidth + dividerWidth + todayIconWidth - : arrowWidth); - } else { - xPosition = localPosition.dx - - (widget.isRTL - ? arrowWidth * 2 - : calendarViewWidth + dividerWidth + todayIconWidth); - } - - setState(() { - _mouseHoverPosition = Offset(xPosition, localPosition.dy); - }); - } } 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 f9541a66b..8dff17281 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 @@ -249,8 +249,7 @@ class _MonthViewRenderObjectWidget extends MultiChildRenderObjectWidget { } } -class _MonthViewRenderObject extends RenderBox - with ContainerRenderObjectMixin { +class _MonthViewRenderObject extends _CustomCalendarRenderObject { _MonthViewRenderObject( this._visibleDates, this._visibleAppointments, @@ -555,20 +554,13 @@ class _MonthViewRenderObject extends RenderBox _calendarCellNotifier?.addListener(markNeedsPaint); } - /// attach will called when the render object removed from view. + /// detach will called when the render object removed from view. @override void detach() { _calendarCellNotifier?.removeListener(markNeedsPaint); super.detach(); } - @override - void setupParentData(RenderObject child) { - if (child.parentData is! _CalendarParentData) { - child.parentData = _CalendarParentData(); - } - } - @override void performLayout() { final Size widgetSize = constraints.biggest; @@ -632,56 +624,6 @@ class _MonthViewRenderObject extends RenderBox } } - @override - void describeSemanticsConfiguration(SemanticsConfiguration config) { - super.describeSemanticsConfiguration(config); - config.isSemanticBoundary = true; - } - - @override - void assembleSemanticsNode( - SemanticsNode node, - SemanticsConfiguration config, - Iterable children, - ) { - final List semantics = _getSemanticsBuilder(size); - final List semanticsNodes = []; - for (int i = 0; i < semantics.length; i++) { - final CustomPainterSemantics currentSemantics = semantics[i]; - final SemanticsNode newChild = SemanticsNode( - key: currentSemantics.key, - ); - - final SemanticsProperties properties = currentSemantics.properties; - final SemanticsConfiguration config = SemanticsConfiguration(); - if (properties.label != null) { - config.label = properties.label; - } - if (properties.textDirection != null) { - config.textDirection = properties.textDirection; - } - - newChild.updateWith( - config: config, - // As of now CustomPainter does not support multiple tree levels. - childrenInInversePaintOrder: const [], - ); - - newChild - ..rect = currentSemantics.rect - ..transform = currentSemantics.transform - ..tags = currentSemantics.tags; - - semanticsNodes.add(newChild); - } - - final List finalChildren = []; - finalChildren.addAll(semanticsNodes); - finalChildren.addAll(children); - - super.assembleSemanticsNode(node, config, finalChildren); - } - Paint _linePainter; TextPainter _textPainter; static const double linePadding = 0.5; @@ -949,7 +891,7 @@ class _MonthViewRenderObject extends RenderBox String _getAccessibilityText(DateTime date, int index) { final String accessibilityText = DateFormat('EEE, dd/MMMM/yyyy').format(date).toString(); - if (_blackoutDatesIndex.contains(index)) { + if (_blackoutDatesIndex != null && _blackoutDatesIndex.contains(index)) { return accessibilityText + ', Blackout date'; } @@ -992,4 +934,8 @@ class _MonthViewRenderObject extends RenderBox return semanticsBuilder; } + + @override + List Function(Size size) get semanticsBuilder => + _getSemanticsBuilder; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/multi_child_container.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/multi_child_container.dart new file mode 100644 index 000000000..83a2da8af --- /dev/null +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/multi_child_container.dart @@ -0,0 +1,193 @@ +part of calendar; + +class _CalendarMultiChildContainer extends Stack { + _CalendarMultiChildContainer( + {this.painter, List children, this.width, this.height}) + : super(children: children); + final CustomPainter painter; + final double width; + final double height; + + @override + RenderStack createRenderObject(BuildContext context) { + return _MultiChildContainerRenderObject(width, height, painter: painter); + } + + @override + void updateRenderObject(BuildContext context, RenderStack renderObject) { + super.updateRenderObject(context, renderObject); + if (renderObject is _MultiChildContainerRenderObject) { + renderObject + ..width = width + ..height = height + ..painter = painter; + } + } +} + +class _MultiChildContainerRenderObject extends RenderStack { + _MultiChildContainerRenderObject(this._width, this._height, + {CustomPainter painter}) + : _painter = painter, + super(); + + CustomPainter get painter => _painter; + CustomPainter _painter; + + set painter(CustomPainter value) { + if (_painter == value) { + return; + } + + final CustomPainter oldPainter = _painter; + _painter = value; + _updatePainter(_painter, oldPainter); + if (attached) { + oldPainter?.removeListener(markNeedsPaint); + _painter?.addListener(markNeedsPaint); + } + } + + double get width => _width; + + set width(double value) { + if (_width == value) { + return; + } + + _width = value; + markNeedsLayout(); + } + + double _width; + double _height; + + double get height => _height; + + set height(double value) { + if (_height == value) { + return; + } + + _height = value; + markNeedsLayout(); + } + + void _updatePainter(CustomPainter newPainter, CustomPainter oldPainter) { + if (newPainter == null) { + markNeedsPaint(); + } else if (oldPainter == null || + newPainter.runtimeType != oldPainter.runtimeType || + newPainter.shouldRepaint(oldPainter)) { + markNeedsPaint(); + } + + if (newPainter == null) { + if (attached) { + markNeedsSemanticsUpdate(); + } + } else if (oldPainter == null || + newPainter.runtimeType != oldPainter.runtimeType || + newPainter.shouldRebuildSemantics(oldPainter)) { + markNeedsSemanticsUpdate(); + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _painter?.addListener(markNeedsPaint); + } + + @override + void detach() { + _painter?.removeListener(markNeedsPaint); + super.detach(); + } + + @override + void performLayout() { + final Size widgetSize = constraints.biggest; + size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height); + for (var child = firstChild; child != null; child = childAfter(child)) { + child.layout(constraints); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + if (_painter != null) { + _painter.paint(context.canvas, size); + } + + paintStack(context, offset); + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config.isSemanticBoundary = true; + } + + @override + void assembleSemanticsNode( + SemanticsNode node, + SemanticsConfiguration config, + Iterable children, + ) { + final List semantics = semanticsBuilder(size); + final List semanticsNodes = []; + for (int i = 0; i < semantics.length; i++) { + final CustomPainterSemantics currentSemantics = semantics[i]; + final SemanticsNode newChild = SemanticsNode( + key: currentSemantics.key, + ); + + final SemanticsProperties properties = currentSemantics.properties; + final SemanticsConfiguration config = SemanticsConfiguration(); + if (properties.label != null) { + config.label = properties.label; + } + if (properties.textDirection != null) { + config.textDirection = properties.textDirection; + } + + newChild.updateWith( + config: config, + // As of now CustomPainter does not support multiple tree levels. + childrenInInversePaintOrder: const [], + ); + + newChild + ..rect = currentSemantics.rect + ..transform = currentSemantics.transform + ..tags = currentSemantics.tags; + + semanticsNodes.add(newChild); + } + + final List finalChildren = []; + finalChildren.addAll(semanticsNodes); + finalChildren.addAll(children); + + super.assembleSemanticsNode(node, config, finalChildren); + } + + SemanticsBuilderCallback get semanticsBuilder { + final List semantics = []; + if (painter != null) { + semantics.addAll(painter.semanticsBuilder(size)); + } + for (RenderRepaintBoundary child = firstChild; + child != null; + child = childAfter(child)) { + final _CustomCalendarRenderObject appointmentRenderObject = child.child; + semantics.addAll(appointmentRenderObject.semanticsBuilder(size)); + } + + return (Size size) { + return semantics; + }; + } +} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/schedule_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/schedule_view.dart index f12c74a75..27e6641a8 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/schedule_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/schedule_view.dart @@ -42,7 +42,7 @@ class _ScheduleLabelPainter extends CustomPainter { this.isMonthLabel, this.isRTL, this.locale, - this.isScheduleWebUI, + this.useMobilePlatformUI, this.agendaViewNotifier, this.calendarTheme, this._localizations, @@ -57,7 +57,7 @@ class _ScheduleLabelPainter extends CustomPainter { final String locale; final ScheduleViewSettings scheduleViewSettings; final SfLocalizations _localizations; - final bool isScheduleWebUI; + final bool useMobilePlatformUI; final ValueNotifier<_ScheduleViewHoveringDetails> agendaViewNotifier; final SfCalendarThemeData calendarTheme; final bool isDisplayDate; @@ -83,7 +83,7 @@ class _ScheduleLabelPainter extends CustomPainter { void _addDisplayDateLabel(Canvas canvas, Size size) { /// Add the localized add new appointment text for display date view. final TextSpan span = TextSpan( - text: _localizations.scheduleViewNewEventLabel, + text: _localizations.noEventsCalendarLabel, style: scheduleViewSettings.weekHeaderSettings.weekTextStyle ?? const TextStyle( color: Colors.grey, fontSize: 15, fontFamily: 'Roboto'), @@ -109,7 +109,16 @@ class _ScheduleLabelPainter extends CustomPainter { isSameDate(agendaViewNotifier.value.hoveringDate, startDate)) { _backgroundPainter ??= Paint(); const double padding = 5; - if (isScheduleWebUI) { + if (useMobilePlatformUI) { + final Rect rect = Rect.fromLTWH( + 0, padding, size.width - 2, size.height - (2 * padding)); + _backgroundPainter.color = + calendarTheme.selectionBorderColor.withOpacity(0.4); + _backgroundPainter.style = PaintingStyle.stroke; + _backgroundPainter.strokeWidth = 2; + canvas.drawRect(rect, _backgroundPainter); + _backgroundPainter.style = PaintingStyle.fill; + } else { const double viewPadding = 2; final Rect rect = Rect.fromLTWH( 0, @@ -119,15 +128,6 @@ class _ScheduleLabelPainter extends CustomPainter { _backgroundPainter.color = Colors.grey.withOpacity(0.1); canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(4)), _backgroundPainter); - } else { - final Rect rect = Rect.fromLTWH( - 0, padding, size.width - 2, size.height - (2 * padding)); - _backgroundPainter.color = - calendarTheme.selectionBorderColor.withOpacity(0.4); - _backgroundPainter.style = PaintingStyle.stroke; - _backgroundPainter.strokeWidth = 2; - canvas.drawRect(rect, _backgroundPainter); - _backgroundPainter.style = PaintingStyle.fill; } } } @@ -280,7 +280,7 @@ class _ScheduleLabelPainter extends CustomPainter { .toString(); } else { cellHeight = size.height; - accessibilityText = _localizations.scheduleViewNewEventLabel; + accessibilityText = _localizations.noEventsCalendarLabel; } } else { cellHeight = scheduleViewSettings.monthHeaderSettings.height; @@ -330,7 +330,6 @@ class _ScheduleAppointmentView extends Stack { RepaintBoundary(child: header) ], alignment: alignment ?? AlignmentDirectional.topStart, - overflow: Overflow.clip, ); @override diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/selection_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/selection_view.dart index 938380192..36086d313 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/selection_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/selection_view.dart @@ -57,10 +57,12 @@ class _SelectionPainter extends CustomPainter { if (view != CalendarView.month && !isTimeline) { width -= timeLabelWidth; } + + final bool isResourceEnabled = + isTimeline && _isResourceEnabled(calendar.dataSource, view); if ((selectedDate == null && _appointmentView == null) || visibleDates != _updateCalendarStateDetails._currentViewVisibleDates || - (_isResourceEnabled(calendar.dataSource, view) && - selectedResourceIndex == -1)) { + (isResourceEnabled && selectedResourceIndex == -1)) { return; } @@ -79,7 +81,7 @@ class _SelectionPainter extends CustomPainter { /// The selection view must render on the resource area alone, when the /// resource enabled. - if (selectedResourceIndex >= 0) { + if (isResourceEnabled && selectedResourceIndex >= 0) { _cellHeight = resourceItemHeight; } } @@ -320,11 +322,18 @@ 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(); + _boxPainter = + selectionDecoration.createBoxPainter(_updateSelectionDecorationPainter); _boxPainter.paint(canvas, Offset(rect.left, rect.top), ImageConfiguration(size: rect.size)); } + /// Used to pass the argument of create box painter and it is called when + /// decoration have asynchronous data like image. + void _updateSelectionDecorationPainter() { + repaintNotifier.value = !repaintNotifier.value; + } + void _drawSlotSelection(double width, double height, Canvas canvas) { //// padding used to avoid first, last row and column selection clipping. const double padding = 0.5; @@ -339,7 +348,8 @@ class _SelectionPainter extends CustomPainter { ? _yPosition + _cellHeight - padding : _yPosition + _cellHeight); - _boxPainter = selectionDecoration.createBoxPainter(); + _boxPainter = + selectionDecoration.createBoxPainter(_updateSelectionDecorationPainter); _boxPainter.paint(canvas, Offset(rect.left, rect.top), ImageConfiguration(size: rect.size, textDirection: TextDirection.ltr)); } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/time_ruler_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/time_ruler_view.dart index 470b6f6a1..2d2c4686c 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/time_ruler_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/time_ruler_view.dart @@ -88,6 +88,10 @@ class _TimeRulerView extends CustomPainter { i <= (isTimelineView ? horizontalLinesCount - 1 : horizontalLinesCount); i++) { if (isTimelineView) { + canvas.save(); + canvas.clipRect( + Rect.fromLTWH(xPosition, 0, timeIntervalHeight, size.height)); + canvas.restore(); canvas.drawLine( Offset(xPosition, 0), Offset(xPosition, size.height), _linePainter); } 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 e53e26e7d..b16b64da3 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/timeline_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/timeline_view.dart @@ -1,22 +1,24 @@ part of calendar; -class _TimelineView extends CustomPainter { - _TimelineView( +class _TimelineWidget extends StatefulWidget { + _TimelineWidget( this.horizontalLinesCountPerView, this.visibleDates, this.timeSlotViewSettings, this.timeIntervalHeight, this.cellBorderColor, this.isRTL, - this.locale, this.calendarTheme, this.calendarCellNotifier, this.scrollController, this.specialRegion, this.resourceItemHeight, this.resourceCollection, - this.textScaleFactor) - : super(repaint: calendarCellNotifier); + this.textScaleFactor, + this.isMobilePlatform, + this.timeRegionBuilder, + this.width, + this.height); final double horizontalLinesCountPerView; final List visibleDates; @@ -24,7 +26,6 @@ class _TimelineView extends CustomPainter { final double timeIntervalHeight; final Color cellBorderColor; final SfCalendarThemeData calendarTheme; - final String locale; final bool isRTL; final ValueNotifier calendarCellNotifier; final ScrollController scrollController; @@ -32,142 +33,108 @@ class _TimelineView extends CustomPainter { final double resourceItemHeight; final List resourceCollection; final double textScaleFactor; - double _x1, _x2, _y1, _y2; - Paint _linePainter; + final bool isMobilePlatform; + final double width; + final double height; + final TimeRegionBuilder timeRegionBuilder; @override - void paint(Canvas canvas, Size size) { - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - final bool isResourceEnabled = - resourceCollection != null && resourceCollection.isNotEmpty; - _x1 = 0; - _x2 = size.width; - _y1 = timeIntervalHeight; - _y2 = timeIntervalHeight; - _linePainter = _linePainter ?? Paint(); - if (specialRegion != null) { - _addSpecialRegion(canvas, size, isResourceEnabled); - } - - _linePainter.strokeWidth = 0.5; - _linePainter.strokeCap = StrokeCap.round; - _linePainter.color = cellBorderColor ?? calendarTheme.cellBorderColor; - _x1 = 0; - _x2 = size.width; - _y1 = 0.5; - _y2 = 0.5; - - final Offset start = Offset(_x1, _y1); - final Offset end = Offset(_x2, _y2); - canvas.drawLine(start, end, _linePainter); - - _x1 = 0; - _x2 = 0; - _y2 = size.height; - if (isRTL) { - _x1 = size.width; - _x2 = size.width; - } - final List points = []; - for (int i = 0; - i < horizontalLinesCountPerView * visibleDates.length; - i++) { - _y1 = 0; - if (kIsWeb) { - canvas.drawLine(Offset(_x1, _y1), Offset(_x2, _y2), _linePainter); - } else { - points.add(Offset(_x1, _y1)); - points.add(Offset(_x2, _y2)); - } + _TimelineWidgetState createState() => _TimelineWidgetState(); +} - if (isRTL) { - _x1 -= timeIntervalHeight; - _x2 -= timeIntervalHeight; - } else { - _x1 += timeIntervalHeight; - _x2 += timeIntervalHeight; - } - } +class _TimelineWidgetState extends State<_TimelineWidget> { + List _children; + List<_TimeRegionView> _specialRegionViews; - if (!kIsWeb) { - canvas.drawPoints(PointMode.lines, points, _linePainter); - } + @override + void initState() { + _children = []; + _updateSpecialRegionDetails(); + super.initState(); + } - /// Draws the vertical line to separate the slots based on resource count. - if (isResourceEnabled) { - _x1 = 0; - _x2 = size.width; - _y1 = resourceItemHeight; - for (int i = 0; i < resourceCollection.length; i++) { - canvas.drawLine(Offset(_x1, _y1), Offset(_x2, _y1), _linePainter); - _y1 += resourceItemHeight; - } + @override + void didUpdateWidget(_TimelineWidget oldWidget) { + if (widget.visibleDates != oldWidget.visibleDates || + widget.timeIntervalHeight != oldWidget.timeIntervalHeight || + widget.timeSlotViewSettings != oldWidget.timeSlotViewSettings || + widget.isRTL != oldWidget.isRTL || + widget.resourceItemHeight != oldWidget.resourceItemHeight || + widget.resourceCollection != oldWidget.resourceCollection || + widget.width != oldWidget.width || + widget.height != oldWidget.height || + widget.timeRegionBuilder != oldWidget.timeRegionBuilder || + !_isCollectionEqual(widget.specialRegion, oldWidget.specialRegion)) { + _updateSpecialRegionDetails(); + _children.clear(); } - if (calendarCellNotifier.value != null) { - _addMouseHovering(canvas, size, isResourceEnabled); - } + super.didUpdateWidget(oldWidget); } - void _addMouseHovering(Canvas canvas, Size size, bool isResourceEnabled) { - double left = (calendarCellNotifier.value.dx ~/ timeIntervalHeight) * - timeIntervalHeight; - double top = 0; - double height = size.height; - if (isResourceEnabled) { - final int index = - (calendarCellNotifier.value.dy / resourceItemHeight).truncate(); - top = index * resourceItemHeight; - height = resourceItemHeight; - } - const double padding = 0.5; - top = top == 0 ? padding : top; - height = height == size.height - ? top == padding - ? height - (padding * 2) - : height - padding - : height; - double width = timeIntervalHeight; - double difference = 0; - if (isRTL && - (size.width - scrollController.offset) < - scrollController.position.viewportDimension) { - difference = scrollController.position.viewportDimension - size.width; - } - - if ((size.width - scrollController.offset) < - scrollController.position.viewportDimension && - (left + timeIntervalHeight).round() == size.width.round()) { - width -= padding; + @override + Widget build(BuildContext context) { + _children ??= []; + if (_children.isEmpty && + widget.timeRegionBuilder != null && + _specialRegionViews != null && + _specialRegionViews.isNotEmpty) { + final int count = _specialRegionViews.length; + for (int i = 0; i < count; i++) { + final _TimeRegionView view = _specialRegionViews[i]; + final Widget child = widget.timeRegionBuilder( + context, + TimeRegionDetails( + region: view.region, + date: widget.visibleDates[view.visibleIndex], + bounds: view.bound)); + + /// Throw exception when builder return widget is null. + assert(child != null, 'Widget must not be null'); + _children.add(RepaintBoundary(child: child)); + } } - _linePainter.style = PaintingStyle.stroke; - _linePainter.strokeWidth = 2; - _linePainter.color = calendarTheme.selectionBorderColor.withOpacity(0.4); - left = left == 0 ? left - difference + padding : left - difference; - canvas.drawRect(Rect.fromLTWH(left, top, width, height), _linePainter); + return _TimelineRenderWidget( + widget.horizontalLinesCountPerView, + widget.visibleDates, + widget.timeSlotViewSettings, + widget.timeIntervalHeight, + widget.cellBorderColor, + widget.isRTL, + widget.calendarTheme, + widget.calendarCellNotifier, + widget.scrollController, + widget.specialRegion, + widget.resourceItemHeight, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.width, + widget.height, + _specialRegionViews, + widgets: _children, + ); } - /// Calculate the position for special regions and draw the special regions - /// in the timeline views . - void _addSpecialRegion(Canvas canvas, Size size, bool isResourceEnabled) { - /// Condition added to check and add the special region for timeline day, - /// timeline week and timeline work week view only, since the special region - /// support not applicable for timeline month view. - if (visibleDates.length > _kNumberOfDaysInWeek) { + void _updateSpecialRegionDetails() { + _specialRegionViews = <_TimeRegionView>[]; + if (widget.visibleDates.length > _kNumberOfDaysInWeek || + widget.specialRegion == null || + widget.specialRegion.isEmpty) { return; } - final double minuteHeight = - timeIntervalHeight / _getTimeInterval(timeSlotViewSettings); - final DateTime startDate = _convertToStartTime(visibleDates[0]); + final double minuteHeight = widget.timeIntervalHeight / + _getTimeInterval(widget.timeSlotViewSettings); + final DateTime startDate = _convertToStartTime(widget.visibleDates[0]); final DateTime endDate = - _convertToEndTime(visibleDates[visibleDates.length - 1]); - _linePainter.style = PaintingStyle.fill; - final double viewWidth = size.width / visibleDates.length; - for (int i = 0; i < specialRegion.length; i++) { - final TimeRegion region = specialRegion[i]; - _linePainter.color = region.color ?? Colors.grey.withOpacity(0.2); + _convertToEndTime(widget.visibleDates[widget.visibleDates.length - 1]); + final double viewWidth = widget.width / widget.visibleDates.length; + final bool isResourceEnabled = widget.resourceCollection != null && + widget.resourceCollection.isNotEmpty; + for (int i = 0; i < widget.specialRegion.length; i++) { + final TimeRegion region = widget.specialRegion[i]; final DateTime regionStartTime = region._actualStartTime; final DateTime regionEndTime = region._actualEndTime; @@ -186,13 +153,14 @@ class _TimelineView extends CustomPainter { continue; } - int startIndex = _getVisibleDateIndex(visibleDates, regionStartTime); - int endIndex = _getVisibleDateIndex(visibleDates, regionEndTime); + int startIndex = + _getVisibleDateIndex(widget.visibleDates, regionStartTime); + int endIndex = _getVisibleDateIndex(widget.visibleDates, regionEndTime); double startXPosition = _getTimeToPosition( Duration( hours: regionStartTime.hour, minutes: regionStartTime.minute), - timeSlotViewSettings, + widget.timeSlotViewSettings, minuteHeight); if (startIndex == -1) { if (startDate.isAfter(regionStartTime)) { @@ -201,8 +169,8 @@ class _TimelineView extends CustomPainter { startIndex = 0; } else { /// Find the next index when the start date as non working date. - for (int k = 1; k < visibleDates.length; k++) { - final DateTime currentDate = visibleDates[k]; + for (int k = 1; k < widget.visibleDates.length; k++) { + final DateTime currentDate = widget.visibleDates[k]; if (currentDate.isBefore(regionStartTime)) { continue; } @@ -223,13 +191,13 @@ class _TimelineView extends CustomPainter { double endXPosition = _getTimeToPosition( Duration(hours: regionEndTime.hour, minutes: regionEndTime.minute), - timeSlotViewSettings, + 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 = visibleDates.length - 2; k >= 0; k--) { - final DateTime _currentDate = visibleDates[k]; + for (int k = widget.visibleDates.length - 2; k >= 0; k--) { + final DateTime _currentDate = widget.visibleDates[k]; if (_currentDate.isAfter(regionEndTime)) { continue; } @@ -239,12 +207,12 @@ class _TimelineView extends CustomPainter { } if (endIndex == -1) { - endIndex = visibleDates.length - 1; + endIndex = widget.visibleDates.length - 1; } } else { /// Set index as visible date end date index when the /// region end date before the visible end date - endIndex = visibleDates.length - 1; + endIndex = widget.visibleDates.length - 1; } /// End date as non working day and its index as previous date index. @@ -258,115 +226,599 @@ class _TimelineView extends CustomPainter { /// Check the start and end position not between the visible hours /// position(not between start and end hour) if ((startPosition <= 0 && endPosition <= 0) || - (startPosition >= size.width && endPosition >= size.width) || + (startPosition >= widget.width && endPosition >= widget.width) || (startPosition == endPosition)) { continue; } - if (isRTL) { - startPosition = size.width - startPosition; - endPosition = size.width - endPosition; + if (widget.isRTL) { + startPosition = widget.width - startPosition; + endPosition = widget.width - endPosition; } double topPosition = 0; - double bottomPosition = size.height; + double bottomPosition = widget.height; if (isResourceEnabled && region.resourceIds != null && region.resourceIds.isNotEmpty) { for (int i = 0; i < region.resourceIds.length; i++) { - final int index = - _getResourceIndex(resourceCollection, region.resourceIds[i]); - topPosition = index * resourceItemHeight; - bottomPosition = topPosition + resourceItemHeight; - _drawSpecialRegion(canvas, size, bottomPosition, endPosition, - startPosition, topPosition, region); + final int index = _getResourceIndex( + widget.resourceCollection, region.resourceIds[i]); + topPosition = index * widget.resourceItemHeight; + bottomPosition = topPosition + widget.resourceItemHeight; + _updateSpecialRegionRect(region, startPosition, endPosition, + topPosition, bottomPosition, startIndex); } } else { - _drawSpecialRegion(canvas, size, bottomPosition, endPosition, - startPosition, topPosition, region); + _updateSpecialRegionRect(region, startPosition, endPosition, + topPosition, bottomPosition, startIndex); } } } - /// Draw the special region based on the calculated positions. - void _drawSpecialRegion( - Canvas canvas, - Size size, - double bottomPosition, - double endPosition, + void _updateSpecialRegionRect( + TimeRegion region, double startPosition, + double endPosition, double topPosition, - TimeRegion region) { - final TextPainter painter = TextPainter( - textDirection: TextDirection.ltr, - maxLines: 1, - textScaleFactor: textScaleFactor, - textAlign: isRTL ? TextAlign.right : TextAlign.left, - textWidthBasis: TextWidthBasis.longestLine); + double bottomPosition, + int index) { Rect rect; - if (isRTL) { + if (widget.isRTL) { rect = Rect.fromLTRB( endPosition, topPosition, startPosition, bottomPosition); } else { rect = Rect.fromLTRB( startPosition, topPosition, endPosition, bottomPosition); } - canvas.drawRect(rect, _linePainter); - if ((region.text == null || region.text.isEmpty) && - region.iconData == null) { + + _specialRegionViews + .add(_TimeRegionView(region: region, visibleIndex: index, bound: rect)); + } +} + +class _TimelineRenderWidget extends MultiChildRenderObjectWidget { + _TimelineRenderWidget( + this.horizontalLinesCountPerView, + this.visibleDates, + this.timeSlotViewSettings, + this.timeIntervalHeight, + 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, + {List widgets}) + : super(children: widgets); + + final double horizontalLinesCountPerView; + final List visibleDates; + final TimeSlotViewSettings timeSlotViewSettings; + final double timeIntervalHeight; + final Color cellBorderColor; + final SfCalendarThemeData calendarTheme; + final bool isRTL; + final ValueNotifier calendarCellNotifier; + final ScrollController scrollController; + final List specialRegion; + final double resourceItemHeight; + final List resourceCollection; + final double textScaleFactor; + final bool isMobilePlatform; + final double width; + final double height; + final List<_TimeRegionView> specialRegionBounds; + + @override + _TimelineRenderObject createRenderObject(BuildContext context) { + return _TimelineRenderObject( + horizontalLinesCountPerView, + visibleDates, + timeSlotViewSettings, + timeIntervalHeight, + cellBorderColor, + isRTL, + calendarTheme, + calendarCellNotifier, + scrollController, + specialRegion, + resourceItemHeight, + resourceCollection, + textScaleFactor, + isMobilePlatform, + width, + height, + specialRegionBounds); + } + + @override + void updateRenderObject( + BuildContext context, _TimelineRenderObject renderObject) { + renderObject + ..horizontalLinesCountPerView = horizontalLinesCountPerView + ..visibleDates = visibleDates + ..timeSlotViewSettings = timeSlotViewSettings + ..timeIntervalHeight = timeIntervalHeight + ..cellBorderColor = cellBorderColor + ..isRTL = isRTL + ..calendarTheme = calendarTheme + ..calendarCellNotifier = calendarCellNotifier + ..scrollController = scrollController + ..specialRegion = specialRegion + ..resourceItemHeight = resourceItemHeight + ..resourceCollection = resourceCollection + ..textScaleFactor = textScaleFactor + ..isMobilePlatform = isMobilePlatform + ..width = width + ..height = height + ..specialRegionBounds = specialRegionBounds; + } +} + +class _TimelineRenderObject extends _CustomCalendarRenderObject { + _TimelineRenderObject( + this._horizontalLinesCountPerView, + this._visibleDates, + this._timeSlotViewSettings, + this._timeIntervalHeight, + 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); + + double _horizontalLinesCountPerView; + + double get horizontalLinesCountPerView => _horizontalLinesCountPerView; + + set horizontalLinesCountPerView(double value) { + if (_horizontalLinesCountPerView == value) { return; } - final TextStyle textStyle = region.textStyle ?? - TextStyle( - color: calendarTheme.brightness != null && - calendarTheme.brightness == Brightness.dark - ? Colors.white54 - : Colors.black45); - painter.textDirection = TextDirection.ltr; - painter.textAlign = isRTL ? TextAlign.right : TextAlign.left; - if (region.iconData == null) { - painter.text = TextSpan(text: region.text, style: textStyle); - painter.ellipsis = '..'; + _horizontalLinesCountPerView = value; + markNeedsPaint(); + } + + List _visibleDates; + + List get visibleDates => _visibleDates; + + set visibleDates(List value) { + if (_visibleDates == value) { + return; + } + + _visibleDates = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + TimeSlotViewSettings _timeSlotViewSettings; + + TimeSlotViewSettings get timeSlotViewSettings => _timeSlotViewSettings; + + set timeSlotViewSettings(TimeSlotViewSettings value) { + if (_timeSlotViewSettings == value) { + return; + } + + _timeSlotViewSettings = value; + if (childCount == 0) { + markNeedsPaint(); } else { - painter.text = TextSpan( - text: String.fromCharCode(region.iconData.codePoint), - style: textStyle.copyWith(fontFamily: region.iconData.fontFamily)); + markNeedsLayout(); + } + } + + double _timeIntervalHeight; + + double get timeIntervalHeight => _timeIntervalHeight; + + set timeIntervalHeight(double value) { + if (_timeIntervalHeight == value) { + return; } - painter.layout(minWidth: 0, maxWidth: rect.width - 4); - painter.paint(canvas, Offset(rect.left + 3, rect.top + 3)); + _timeIntervalHeight = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } } + Color _cellBorderColor; + + Color get cellBorderColor => _cellBorderColor; + + set cellBorderColor(Color value) { + if (_cellBorderColor == value) { + return; + } + + _cellBorderColor = value; + markNeedsPaint(); + } + + SfCalendarThemeData _calendarTheme; + + SfCalendarThemeData get calendarTheme => _calendarTheme; + + set calendarTheme(SfCalendarThemeData value) { + if (_calendarTheme == value) { + return; + } + + _calendarTheme = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + double _resourceItemHeight; + + double get resourceItemHeight => _resourceItemHeight; + + set resourceItemHeight(double value) { + if (_resourceItemHeight == value) { + return; + } + + _resourceItemHeight = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + List resourceCollection; + + bool _isRTL; + + bool get isRTL => _isRTL; + + set isRTL(bool value) { + if (_isRTL == value) { + return; + } + + _isRTL = value; + markNeedsPaint(); + } + + ValueNotifier _calendarCellNotifier; + + ValueNotifier get calendarCellNotifier => _calendarCellNotifier; + + set calendarCellNotifier(ValueNotifier value) { + if (_calendarCellNotifier == value) { + return; + } + + _calendarCellNotifier?.removeListener(markNeedsPaint); + _calendarCellNotifier = value; + _calendarCellNotifier?.addListener(markNeedsPaint); + } + + double _width; + + double get width => _width; + + set width(double value) { + if (_width == value) { + return; + } + + _width = value; + markNeedsLayout(); + } + + double _height; + + double get height => _height; + + set height(double value) { + if (_height == value) { + return; + } + + _height = value; + markNeedsLayout(); + } + + double _textScaleFactor; + + double get textScaleFactor => _textScaleFactor; + + set textScaleFactor(double value) { + if (_textScaleFactor == value) { + return; + } + + _textScaleFactor = value; + markNeedsPaint(); + } + + List _specialRegion; + + List get specialRegion => _specialRegion; + + set specialRegion(List value) { + if (_isCollectionEqual(_specialRegion, value)) { + return; + } + + _specialRegion = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + List<_TimeRegionView> specialRegionBounds; + ScrollController scrollController; + bool isMobilePlatform; + Paint _linePainter; + @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _TimelineView oldWidget = oldDelegate; - return oldWidget.horizontalLinesCountPerView != - horizontalLinesCountPerView || - oldWidget.visibleDates != visibleDates || - oldWidget.cellBorderColor != cellBorderColor || - oldWidget.calendarTheme != calendarTheme || - oldWidget.timeSlotViewSettings != timeSlotViewSettings || - oldWidget.isRTL != isRTL || - oldWidget.locale != locale || - oldWidget.calendarCellNotifier.value != calendarCellNotifier.value || - oldWidget.resourceItemHeight != resourceItemHeight || - oldWidget.resourceCollection != resourceCollection || - oldWidget.textScaleFactor != textScaleFactor || - oldWidget.specialRegion != specialRegion; + bool get isRepaintBoundary => true; + + /// attach will called when the render object rendered in view. + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _calendarCellNotifier?.addListener(markNeedsPaint); } - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the list - /// of custom painter semantics which contains the rect area and the semantics - /// properties for accessibility + /// detach will called when the render object removed from view. @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilder(size); - }; + void detach() { + _calendarCellNotifier?.removeListener(markNeedsPaint); + super.detach(); + } + + @override + void performLayout() { + final Size widgetSize = constraints.biggest; + size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height); + RenderBox child = firstChild; + if (specialRegion == null || specialRegion.isEmpty) { + return; + } + + final int count = specialRegionBounds.length; + for (int i = 0; i < count; i++) { + final _TimeRegionView view = specialRegionBounds[i]; + if (child == null) { + continue; + } + final Rect rect = view.bound; + child.layout(constraints.copyWith( + minHeight: rect.height, + maxHeight: rect.height, + minWidth: rect.width, + maxWidth: rect.width)); + child = childAfter(child); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + RenderBox child = firstChild; + final bool isNeedDefaultPaint = childCount == 0; + _linePainter = _linePainter ?? Paint(); + final bool isResourceEnabled = + resourceCollection != null && resourceCollection.isNotEmpty; + if (isNeedDefaultPaint) { + _addSpecialRegion(context.canvas, isResourceEnabled); + } else { + if (specialRegion == null || specialRegion.isEmpty) { + return; + } + + final int count = specialRegionBounds.length; + for (int i = 0; i < count; i++) { + final _TimeRegionView view = specialRegionBounds[i]; + if (child == null) { + continue; + } + final Rect rect = view.bound; + child.paint(context, Offset(rect.left, rect.top)); + child = childAfter(child); + } + } + + _drawTimeline(context.canvas, isResourceEnabled); + } + + void _drawTimeline(Canvas canvas, bool isResourceEnabled) { + double startXPosition = 0; + double endXPosition = size.width; + double startYPosition = timeIntervalHeight; + double endYPosition = timeIntervalHeight; + _linePainter.strokeWidth = 0.5; + _linePainter.strokeCap = StrokeCap.round; + _linePainter.color = cellBorderColor ?? calendarTheme.cellBorderColor; + startXPosition = 0; + endXPosition = size.width; + startYPosition = 0.5; + endYPosition = 0.5; + + final Offset start = Offset(startXPosition, startYPosition); + final Offset end = Offset(endXPosition, endYPosition); + canvas.drawLine(start, end, _linePainter); + + startXPosition = 0; + endXPosition = 0; + startYPosition = 0; + endYPosition = size.height; + if (isRTL) { + startXPosition = size.width; + endXPosition = size.width; + } + + final List points = []; + for (int i = 0; + i < horizontalLinesCountPerView * visibleDates.length; + i++) { + if (isMobilePlatform) { + points.add(Offset(startXPosition, startYPosition)); + points.add(Offset(endXPosition, endYPosition)); + } else { + canvas.drawLine(Offset(startXPosition, startYPosition), + Offset(endXPosition, endYPosition), _linePainter); + } + + if (isRTL) { + startXPosition -= timeIntervalHeight; + endXPosition -= timeIntervalHeight; + } else { + startXPosition += timeIntervalHeight; + endXPosition += timeIntervalHeight; + } + } + + if (isMobilePlatform) { + canvas.drawPoints(PointMode.lines, points, _linePainter); + } + + /// Draws the vertical line to separate the slots based on resource count. + if (isResourceEnabled) { + startXPosition = 0; + endXPosition = size.width; + startYPosition = resourceItemHeight; + for (int i = 0; i < resourceCollection.length; i++) { + canvas.drawLine(Offset(startXPosition, startYPosition), + Offset(endXPosition, startYPosition), _linePainter); + startYPosition += resourceItemHeight; + } + } + + if (calendarCellNotifier.value != null) { + _addMouseHovering(canvas, size, isResourceEnabled); + } } + void _addMouseHovering(Canvas canvas, Size size, bool isResourceEnabled) { + double left = (calendarCellNotifier.value.dx ~/ timeIntervalHeight) * + timeIntervalHeight; + double top = 0; + double height = size.height; + if (isResourceEnabled) { + final int index = + (calendarCellNotifier.value.dy / resourceItemHeight).truncate(); + top = index * resourceItemHeight; + height = resourceItemHeight; + } + const double padding = 0.5; + top = top == 0 ? padding : top; + height = height == size.height + ? top == padding + ? height - (padding * 2) + : height - padding + : height; + double width = timeIntervalHeight; + double difference = 0; + if (isRTL && + (size.width - scrollController.offset) < + scrollController.position.viewportDimension) { + difference = scrollController.position.viewportDimension - size.width; + } + + if ((size.width - scrollController.offset) < + scrollController.position.viewportDimension && + (left + timeIntervalHeight).round() == size.width.round()) { + width -= padding; + } + + _linePainter.style = PaintingStyle.stroke; + _linePainter.strokeWidth = 2; + _linePainter.color = calendarTheme.selectionBorderColor.withOpacity(0.4); + left = left == 0 ? left - difference + padding : left - difference; + canvas.drawRect(Rect.fromLTWH(left, top, width, height), _linePainter); + } + + /// Calculate the position for special regions and draw the special regions + /// in the timeline views . + void _addSpecialRegion(Canvas canvas, bool isResourceEnabled) { + /// Condition added to check and add the special region for timeline day, + /// timeline week and timeline work week view only, since the special region + /// support not applicable for timeline month view. + if (visibleDates.length > _kNumberOfDaysInWeek || + _specialRegion == null || + _specialRegion.isEmpty) { + return; + } + + final TextPainter painter = TextPainter( + textDirection: TextDirection.ltr, + maxLines: 1, + textScaleFactor: textScaleFactor, + textAlign: isRTL ? TextAlign.right : TextAlign.left, + textWidthBasis: TextWidthBasis.longestLine); + + _linePainter.style = PaintingStyle.fill; + final int count = specialRegionBounds.length; + for (int i = 0; i < count; i++) { + final _TimeRegionView view = specialRegionBounds[i]; + final TimeRegion region = view.region; + _linePainter.color = region.color ?? Colors.grey.withOpacity(0.2); + final TextStyle textStyle = region.textStyle ?? + TextStyle( + color: calendarTheme.brightness != null && + calendarTheme.brightness == Brightness.dark + ? Colors.white54 + : Colors.black45); + final Rect rect = view.bound; + canvas.drawRect(rect, _linePainter); + if ((region.text == null || region.text.isEmpty) && + region.iconData == null) { + return; + } + + if (region.iconData == null) { + painter.text = TextSpan(text: region.text, style: textStyle); + painter.ellipsis = '..'; + } else { + painter.text = TextSpan( + text: String.fromCharCode(region.iconData.codePoint), + style: textStyle.copyWith(fontFamily: region.iconData.fontFamily)); + } + + painter.layout(minWidth: 0, maxWidth: rect.width - 4); + painter.paint(canvas, Offset(rect.left + 3, rect.top + 3)); + } + } + + @override + List Function(Size size) get semanticsBuilder => + _getSemanticsBuilder; + List _getSemanticsBuilder(Size size) { List semanticsBuilder = []; final bool isResourceEnabled = @@ -433,12 +885,6 @@ class _TimelineView extends CustomPainter { return dateText; } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _TimelineView oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates; - } } class _TimelineViewHeaderView extends CustomPainter { @@ -499,7 +945,7 @@ class _TimelineViewHeaderView extends CustomPainter { ? timelineViewHeaderScrollController.offset : isRTL ? size.width - childWidth - : _xPosition; + : 0; TextStyle viewHeaderDayTextStyle = calendarTheme.brightness == Brightness.light @@ -632,6 +1078,7 @@ class _TimelineViewHeaderView extends CustomPainter { dayTextPainter.layout(minWidth: 0, maxWidth: childWidth); dateTextPainter.layout(minWidth: 0, maxWidth: childWidth); if (isTimelineMonth) { + canvas.save(); _drawTimelineMonthViewHeader(canvas, childWidth, size, isBlackoutDate); } else { _drawTimelineTimeSlotsViewHeader(canvas, size, childWidth, index, i); @@ -688,6 +1135,7 @@ class _TimelineViewHeaderView extends CustomPainter { void _drawTimelineMonthViewHeader( 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 + (childWidth - @@ -713,6 +1161,7 @@ class _TimelineViewHeaderView extends CustomPainter { } _hoverPainter.color = cellBorderColor ?? calendarTheme.cellBorderColor; + canvas.restore(); canvas.drawLine( Offset(_xPosition, 0), Offset(_xPosition, size.height), _hoverPainter); } diff --git a/packages/syncfusion_flutter_calendar/pubspec.yaml b/packages/syncfusion_flutter_calendar/pubspec.yaml index 45361b87e..8d88d93f0 100644 --- a/packages/syncfusion_flutter_calendar/pubspec.yaml +++ b/packages/syncfusion_flutter_calendar/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - timezone: 0.5.7 + timezone: 0.5.9 syncfusion_flutter_core: path: ../syncfusion_flutter_core diff --git a/packages/syncfusion_flutter_charts/CHANGELOG.md b/packages/syncfusion_flutter_charts/CHANGELOG.md index 37da461ce..ffa56903a 100644 --- a/packages/syncfusion_flutter_charts/CHANGELOG.md +++ b/packages/syncfusion_flutter_charts/CHANGELOG.md @@ -1,3 +1,75 @@ + ## Unreleased + +### Chart + +**Features** + +* Support for defining the maximum width of the axis labels is provided. +* Provided template support for the trackball. +* Support for converting a logical pixel value to a chart data point and vice versa has been provided. +* Now, you can get the `viewportPointIndex` from `onDataLabelTapped`, `onSelectionChanged` and other applicable events. +* Provided `maximumZoomLevel` support for pinch-zooming in the Cartesian chart. + +### Spark Charts `Preview` + +**Features** + +* Provided support for Line, Area, Column, and Win-loss chart types. +* Provided support for Numeric, Category, and Date-time axis types. +* Provided marker and data label supports. +* Provided trackball support to display additional information about the data points. +* Provided plot band support to highlight a particular vertical range. + +## [18.3.52] - 12/01/2020 + +**Bugs** +* Now the zooming will reset properly on the zoom out. +* The legend's width and height properties will work as intended. +* The trackball tooltip will not throw an exception when the tooltip is hidden using `onTrackballPositionChanging` event. + +## [18.3.50] - 11/17/2020 + +**Features** +* Now, we can get the `overallPointIndex` from `onDataLabelTapped` and `onSelectionChanged` events. +* Provided `maximumZoomLevel` support for pinch-zooming in the cartesian chart. + +## [18.3.48+1] - 11/12/2020 + +**Bugs** +* The selection is working properly with `initialSelectedDataIndexes` property. + +## [18.3.48] - 11/11/2020 + +**Bugs** +* The trackball is showing properly with public methods. + +## [18.3.47] - 11/05/2020 + +**Bugs** +* The tooltip builder will not throw any exceptions in Circular charts. + +## [18.3.44] - 10/27/2020 + +**Bugs** +* The zoomed column chart with custom tooltip will not throw any exceptions. +* Now, the rounded corners will be applied properly to the column type charts. + +## [18.3.42] - 10/19/2020 + +**Bugs** +* Now, after resetting the zoomed chart using the public method, the visible range can be set. +* The circular chart will not throw any exception when wrapped with the Column widget. + +## [18.3.40] - 10/13/2020 + +**Bugs** +* Now the chart series will not animate on resetting the zoom. + +## [18.3.38] - 10/07/2020 + +**Bugs** +* Now the spline area series will animate properly on adding new data points dynamically. + ## [18.3.35] - 10/01/2020 **Features** diff --git a/packages/syncfusion_flutter_charts/README.md b/packages/syncfusion_flutter_charts/README.md index 273e81641..09b8ad693 100644 --- a/packages/syncfusion_flutter_charts/README.md +++ b/packages/syncfusion_flutter_charts/README.md @@ -8,26 +8,42 @@ Syncfusion Flutter Charts is a data visualization library written natively in Da Create various types of cartesian or circular charts with seamless interaction, responsiveness, and smooth animation. It has a rich set of features, and it is completely customizable and extendable. +This [syncfusion_flutter_charts](https://pub.dev/packages/syncfusion_flutter_charts) package includes the following widgets + +* [SfCartesianChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCartesianChart-class.html) +* [SfCircularChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCircularChart-class.html) +* [SfPyramidChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfPyramidChart-class.html) +* [SfFunnelChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfFunnelChart-class.html) +* [SfSparkLineChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/sparkcharts/SfSparkLineChart-class.html) +* [SfSparkAreaChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/sparkcharts/SfSparkAreaChart-class.html) +* [SfSparkBarChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/sparkcharts/SfSparkBarChart-class.html) +* [SfSparkWinLossChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/sparkcharts/SfSparkWinLossChart-class.html) + **Disclaimer:** This is a commercial package. To use this package, you need to have either Syncfusion Commercial License or Syncfusion Community license. For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. **Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. ## Table of contents - [Chart features](#chart-features) +- [Spark Charts features](#spark-charts-features) - [Get the demo application](#get-the-demo-application) - [Useful links](#useful-links) - [Installation](#installation) -- [Getting started](#getting-started) +- [Chart getting started](#chart-getting-started) - [Add chart to the widget tree](#add-chart-to-the-widget-tree) - [Bind data source](#bind-data-source) - [Add chart elements](#add-chart-elements) +- [Spark Charts getting started](#spark-charts-getting-started) + - [Add spark charts to the widget tree](#add-spark-charts-to-the-widget-tree) + - [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) ## Chart features * **Chart types** - Provides functionality for rendering 25+ chart types, namely line, spline, column, bar, area, bubble, scatter, step line, fast line, range column, range area, step area, spline area, stacked charts, 100% stacked charts, pie, doughnut, radial bar, pyramid, funnel, etc. Each chart type is easily configured and customized with built-in features for creating stunning visual effects. -![flutter_chart_types](https://cdn.syncfusion.com/content/images/FTControl/Charts/charttypes_till_100Stacked_series.png) +![flutter_chart_types](https://cdn.syncfusion.com/content/images/FTControl/Charts/chart_types.png) * **Axis types** - Plot various types of data in a graph with the help of numeric, category, date-time and log axis types. The built-in axis features allow to customize an axis elements further to make the axis more readable. ![flutter_chart_axis_types](https://cdn.syncfusion.com/content/images/FTControl/chart-axis-types.png) @@ -41,6 +57,29 @@ Create various types of cartesian or circular charts with seamless interaction, * **Dynamic update** - Updates the chart dynamically with live data that changes over seconds or minutes like stock prices, temperature, speed, etc. ![flutter_chart_user_interactions](https://cdn.syncfusion.com/content/images/FTControl/Charts/live_updates.gif) +## Spark Charts features + +Spark charts (micro charts) are lightweight charts that fit in a very small area. They display the trend of the data and convey quick information to the user. + +* **Chart types** - Support to render line, area, column and win-loss chart types. +![spark_chart_types](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_types.jpg) + +* **Axis types** - Spark charts provides support for numeric, category and date-time axes. +![spark_chart_axis_types](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_axis_types.jpg) + +* **Markers and data labels** - Support to render markers and data labels on high, low, first, last and all data points. +![spark_chart_markers_data_label](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_marker_data_label.jpg) + +* **Trackball** - Display additional information about data points on interaction with the chart. +![spark_chart_trackball](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_trackball.gif) + +* **Plot band** - Highlight a particular vertical range using a specific color. +![spark_chart_plotband](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_plotband.jpg) + +* **Live update** - Spark charts can be used in the live update. +![spark_chart_live](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_live_update.gif) + + ## 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. @@ -65,7 +104,7 @@ Take a look at the following to learn more about Syncfusion Flutter charts: Install the latest version from [pub](https://pub.dartlang.org/packages/syncfusion_flutter_charts#-installing-tab-). -## Getting started +## Chart getting started Import the following package. @@ -82,13 +121,19 @@ Widget build(BuildContext context) { return Scaffold( body: Center( child: Container( - child: SfCartesianChart( - ) + child: SfCartesianChart() ) ) ); } ``` + +**Note** + +* Use `SfCartesianChart` widget to render line, spline, area, column, bar, bubble, scatter, step line, and fast line charts. +* Use `SfCircularChart` widget to render pie, doughnut, and radial bar charts. +* Use `SfPyramidChart` and `SfFunnelChart` to render pyramid and funnel charts respectively. + ### Bind data source Based on data, initialize the appropriate axis type and series type. In the series, map the data source and the fields for x and y data points. To render a line chart with category axis, initialize appropriate properties. @@ -131,12 +176,6 @@ class SalesData { ``` -**Note** - -* Use `SfCartesianChart` widget to render line, spline, area, column, bar, bubble, scatter, step line, and fast line charts. -* Use `SfCircularChart` widget to render pie, doughnut, and radial bar charts. -* Use `SfPyramidChart` and `SfFunnelChart` to render pyramid and funnel charts respectively. - ### Add chart elements Add the chart elements such as title, legend, data label, and tooltip to display additional information about the data plotted in the chart. @@ -183,6 +222,87 @@ The following screenshot illustrates the result of the above code sample. ![simple line chart](https://cdn.syncfusion.com/content/images/FTControl/simple-line-chart.gif) +## Spark Charts getting started + +Import the following package. + +```dart +import 'package:syncfusion_flutter_charts/sparkcharts.dart'; +``` + +### Add spark charts to the widget tree + +Add the spark charts widget as a child of any widget. Here, the spark charts widget is added as a child of container widget. + +```dart +@override +Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Container( + child: SfSparkLineChart() + ) + ) + ); +} +``` + +**Note** + +Use `SfSparkAreaChart`, `SfSparkColumnChart` and `SfSparkWinLossChart` widgets to render area, column and win-loss charts respectively. + +### Bind spark charts data source +Based on data and your requirement, initialize the series and bind the data to spark charts. + +```dart +@override +Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Container( + child: SfSparkLineChart( + data: [ + 1, 5, -6, 0, 1, -2, 7, -7, -4, -10, 13, -6, 7, 5, 11, 5, 3 + ], + ) + ) + ) + ); +} +``` + +**Note:** Needs to add the data source to render a spark chart. + +### Add spark charts elements + +Add the spark charts elements such as marker, data label, and trackball to display additional information about the data plotted in the spark charts. + +```dart +@override +Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Container( + child: SfSparkLineChart( + //Enable the trackball + trackball: SparkChartTrackball( + activationMode: SparkChartActivationMode.tap), + //Enable marker + marker: SparkChartMarker( + displayMode: MarkerDisplayMode.all), + //Enable data label + labelDisplayMode: LabelDisplayMode.all, + data: [ + 1, 5, -6, 0, 1, -2, 7, -7, -4, -10, 13, -6, 7, 5, 11, 5, 3 + ], + ) + ) + ) + ); +} +``` +![spark_chart_default_line](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_marker_data_label.jpg) + ## Support and Feedback * For any other queries, reach our [Syncfusion support team](https://www.syncfusion.com/support/directtrac/incidents/newincident) or post the queries through the [Community forums](https://www.syncfusion.com/forums) and submit a feature request or a bug through our [Feedback portal](https://www.syncfusion.com/feedback/flutter). diff --git a/packages/syncfusion_flutter_charts/example/lib/main.dart b/packages/syncfusion_flutter_charts/example/lib/main.dart index 9a474bc44..a3e853a5a 100644 --- a/packages/syncfusion_flutter_charts/example/lib/main.dart +++ b/packages/syncfusion_flutter_charts/example/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/sparkcharts.dart'; void main() { return runApp(_ChartApp()); @@ -9,7 +10,6 @@ class _ChartApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Chart Demo', theme: ThemeData(primarySwatch: Colors.blue), home: _MyHomePage(), ); @@ -25,34 +25,56 @@ class _MyHomePage extends StatefulWidget { } class _MyHomePageState extends State<_MyHomePage> { + List<_SalesData> data = [ + _SalesData('Jan', 35), + _SalesData('Feb', 28), + _SalesData('Mar', 34), + _SalesData('Apr', 32), + _SalesData('May', 40) + ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Syncfusion Flutter chart'), ), - body: 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: <_SalesData>[ - _SalesData('Jan', 35), - _SalesData('Feb', 28), - _SalesData('Mar', 34), - _SalesData('Apr', 32), - _SalesData('May', 40) - ], - xValueMapper: (_SalesData sales, _) => sales.year, - yValueMapper: (_SalesData sales, _) => sales.sales, - // Enable data label - dataLabelSettings: DataLabelSettings(isVisible: true)) - ])); + 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)) + ]), + Padding( + padding: const EdgeInsets.all(8.0), + //Initialize the spark charts widget + child: SfSparkLineChart.custom( + //Enable the trackball + trackball: SparkChartTrackball( + activationMode: SparkChartActivationMode.tap), + //Enable marker + marker: SparkChartMarker( + displayMode: SparkChartMarkerDisplayMode.all), + //Enable data label + labelDisplayMode: SparkChartLabelDisplayMode.all, + xValueMapper: (int index) => data[index].year, + yValueMapper: (int index) => data[index].sales, + dataCount: 5, + ), + ) + ])); } } diff --git a/packages/syncfusion_flutter_charts/lib/charts.dart b/packages/syncfusion_flutter_charts/lib/charts.dart index 36f59cb5a..60aa9da25 100644 --- a/packages/syncfusion_flutter_charts/lib/charts.dart +++ b/packages/syncfusion_flutter_charts/lib/charts.dart @@ -185,6 +185,7 @@ part './src/chart/user_interaction/tooltip_painter.dart'; part './src/chart/user_interaction/tooltip_template.dart'; part './src/chart/user_interaction/trackball.dart'; part './src/chart/user_interaction/trackball_painter.dart'; +part './src/chart/user_interaction/trackball_template.dart'; part './src/chart/user_interaction/zooming_painter.dart'; part './src/chart/user_interaction/zooming_panning.dart'; part './src/chart/utils/enum.dart'; diff --git a/packages/syncfusion_flutter_charts/lib/sparkcharts.dart b/packages/syncfusion_flutter_charts/lib/sparkcharts.dart new file mode 100644 index 000000000..6e8ffc109 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/sparkcharts.dart @@ -0,0 +1,12 @@ +library sparkcharts; + +// export spark chart library + +export 'src/sparkline/marker.dart'; +export 'src/sparkline/plot_band.dart'; +export 'src/sparkline/series/spark_area_base.dart'; +export 'src/sparkline/series/spark_bar_base.dart'; +export 'src/sparkline/series/spark_line_base.dart'; +export 'src/sparkline/series/spark_win_loss_base.dart'; +export 'src/sparkline/trackball/spark_chart_trackball.dart'; +export 'src/sparkline/utils/enum.dart'; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart index f30ec6548..1dd26eb12 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart @@ -43,6 +43,8 @@ abstract class ChartAxis { bool placeLabelsNearAxisLine, List plotBands, this.rangeController, + double maximumLabelWidth, + double labelsExtent, }) : isVisible = isVisible ?? true, anchorRangeToVisiblePoints = anchorRangeToVisiblePoints ?? true, interactiveTooltip = interactiveTooltip ?? InteractiveTooltip(), @@ -75,7 +77,9 @@ abstract class ChartAxis { zoomFactor = zoomFactor ?? 1, zoomPosition = zoomPosition ?? 0, enableAutoIntervalOnZooming = enableAutoIntervalOnZooming ?? true, - plotBands = plotBands ?? []; + plotBands = plotBands ?? [], + maximumLabelWidth = maximumLabelWidth, + labelsExtent = labelsExtent; ///Toggles the visibility of the axis. /// @@ -598,6 +602,77 @@ abstract class ChartAxis { ///} ///``` 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; } /// Holds the axis label information. @@ -608,7 +683,8 @@ abstract class ChartAxis { /// class AxisLabel { /// Creating an argument constructor of AxisLabel class. - AxisLabel(this.labelStyle, this.labelSize, this.text, this.value); + AxisLabel(this.labelStyle, this.labelSize, this.text, this.value, + this.trimmedText, this.renderText); /// Specifies the label text style. /// @@ -631,6 +707,12 @@ class AxisLabel { /// 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; @@ -1308,7 +1390,8 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { if (axis.majorGridLines.width > 0 && renderType == 'outside' && (axis.plotOffset > 0 || - (i != 0 && i != length - 1) || + (i != 0 && + (isBetweenTicks ? i != length - 1 : i != length)) || (axisBounds.left <= pointX && axisBounds.right >= pointX && !_chartState._requireInvertedAxis))) { @@ -1654,7 +1737,8 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { num tempInterval, pointX, pointY, previousLabelEnd; for (int i = 0; i < visibleLabels.length; i++) { final AxisLabel label = visibleLabels[i]; - final String labelText = axisRenderer.getAxisLabel(axis, label.text, i); + final String labelText = + axisRenderer.getAxisLabel(axis, label.renderText, i); textStyle = label.labelStyle; textStyle = _getTextStyle( @@ -1770,7 +1854,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { num tempInterval, pointX, pointY, previousEnd; for (int i = 0; i < visibleLabels.length; i++) { final String labelText = - axisRenderer.getAxisLabel(axis, visibleLabels[i].text, i); + axisRenderer.getAxisLabel(axis, visibleLabels[i].renderText, i); final int angle = axisRenderer.getAxisLabelAngle(axisRenderer, labelText, i); assert(angle - angle.floor() == 0, @@ -2073,8 +2157,20 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { : axisBounds.left + textSize.height / 2; } } - axisRenderer._visibleLabels[i]._labelRegion = + final Rect currentRegion = Rect.fromLTWH(pointX, pointY, textSize.width, textSize.height); + final bool isIntersect = i > 0 && + i < axisRenderer._visibleLabels.length - 1 && + axis.labelIntersectAction == AxisLabelIntersectAction.hide && + axis.labelRotation % 180 == 0 && + axisRenderer._visibleLabels[i - 1]._labelRegion != null && + (axisRenderer._axis.isInversed == false + ? currentRegion.left < + axisRenderer._visibleLabels[i - 1]._labelRegion.right + : currentRegion.right > + axisRenderer._visibleLabels[i - 1]._labelRegion.left); + axisRenderer._visibleLabels[i]._labelRegion = + !isIntersect ? currentRegion : null; return pointX; } @@ -2461,18 +2557,50 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { void _triggerLabelRenderEvent(String labelText, num labelValue) { AxisLabelRenderArgs axisLabelArgs; TextStyle fontStyle = _axis.labelStyle; + + final String actualText = labelText; + Size textSize = _measureText(labelText, _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 = _trimAxisLabelsText( + labelText, + _axis.labelsExtent ?? _axis.maximumLabelWidth, + _axis.labelStyle, + this); + } + textSize = _measureText(labelText, _axis.labelStyle, 0); + } + final String trimmedText = + labelText.contains('...') || labelText.isEmpty ? labelText : null; + String renderText = trimmedText ?? actualText; if (_chart.onAxisLabelRender != null) { axisLabelArgs = AxisLabelRenderArgs(labelValue, _name, _orientation, _axis); - axisLabelArgs.text = labelText; + axisLabelArgs.text = actualText; axisLabelArgs.textStyle = fontStyle; + // axisLabelArgs.trimmedText = trimmedText; _chart.onAxisLabelRender(axisLabelArgs); - labelText = axisLabelArgs.text; fontStyle = axisLabelArgs.textStyle; + // if (actualText != axisLabelArgs.text && trimmedText == null) { + renderText = axisLabelArgs.text; + // } else if (trimmedText != axisLabelArgs.trimmedText && + // trimmedText != null) { + // renderText = axisLabelArgs.trimmedText; + // } } final Size labelSize = - _measureText(labelText, fontStyle, _axis.labelRotation); - _visibleLabels.add(AxisLabel(fontStyle, labelSize, labelText, labelValue)); + _measureText(renderText, fontStyle, _axis.labelRotation); + _visibleLabels.add(AxisLabel( + fontStyle, labelSize, actualText, labelValue, trimmedText, renderText)); } /// Calculate the maximum lable's size @@ -2609,9 +2737,9 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { break; case AxisLabelIntersectAction.wrap: label._labelCollection = _gettingLabelCollection( - label.text, labelMaximumWidth, axisRenderer); + label.renderText, labelMaximumWidth, axisRenderer); if (label._labelCollection.isNotEmpty) { - label.text = label._labelCollection[0]; + label.renderText = label._labelCollection[0]; } height = label.labelSize.height * label._labelCollection.length; if (height > maximumLabelHeight) { @@ -2657,7 +2785,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { /// To get the label collection List _gettingLabelCollection( - String currentLabel, num maximumWidth, ChartAxisRenderer axisRenderer) { + String currentLabel, num labelsExtent, ChartAxisRenderer axisRenderer) { final ChartAxis axis = axisRenderer._axis; final List textCollection = currentLabel.split(RegExp(' ')); final List labelCollection = []; @@ -2665,35 +2793,14 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { for (int i = 0; i < textCollection.length; i++) { text = textCollection[i]; (_measureText(text, axis.labelStyle, axisRenderer._labelRotation).width < - maximumWidth) + labelsExtent) ? labelCollection.add(text) : labelCollection.add(_trimAxisLabelsText( - text, maximumWidth, axis.labelStyle, axisRenderer)); + text, labelsExtent, axis.labelStyle, axisRenderer)); } return labelCollection; } - /// To trim the specific label text - String _trimAxisLabelsText(String text, num maximumWidth, - TextStyle labelStyle, ChartAxisRenderer axisRenderer) { - String label = text; - num size = _measureText( - text, axisRenderer._axis.labelStyle, axisRenderer._labelRotation) - .width; - if (size > maximumWidth) { - final int textLength = text.length; - for (int i = textLength - 1; i >= 0; --i) { - label = text.substring(0, i) + '...'; - size = - _measureText(label, labelStyle, axisRenderer._labelRotation).width; - if (size <= maximumWidth) { - return label; - } - } - } - return label; - } - ///Below method is for changing range while zooming void _calculateZoomRange(ChartAxisRenderer axisRenderer, Size axisSize) { ChartAxisRenderer oldAxisRenderer; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_panel.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_panel.dart index 521b81dd8..79e5c9c69 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_panel.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_panel.dart @@ -175,9 +175,10 @@ class _ChartAxis { ? 0 : (axisRenderer._orientation == AxisOrientation.horizontal) ? axisRenderer._maximumLabelSize.height - : axisRenderer._maximumLabelSize.width) + + : (axis.labelsExtent != null && axis.labelsExtent > 0) + ? axis.labelsExtent + : axisRenderer._maximumLabelSize.width) + _innerPadding; - axisRenderer._totalSize = titleSize + tickSize + labelSize; if (axisRenderer._orientation == AxisOrientation.horizontal) { if (!axis.opposedPosition) { diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/category_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/category_axis.dart index bfc90eb9c..57f58633f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/category_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/category_axis.dart @@ -46,7 +46,9 @@ class CategoryAxis extends ChartAxis { bool placeLabelsNearAxisLine, List plotBands, int desiredIntervals, - RangeController rangeController}) + RangeController rangeController, + double maximumLabelWidth, + double labelsExtent}) : arrangeByIndex = arrangeByIndex ?? false, labelPlacement = labelPlacement ?? LabelPlacement.betweenTicks, super( @@ -81,6 +83,8 @@ class CategoryAxis extends ChartAxis { plotBands: plotBands, desiredIntervals: desiredIntervals, rangeController: rangeController, + maximumLabelWidth: maximumLabelWidth, + labelsExtent: labelsExtent, ); ///Position of the category axis labels. @@ -218,8 +222,8 @@ class CategoryAxisRenderer extends ChartAxisRenderer { !seriesType.contains('range') && !seriesType.contains('hilo') && !seriesType.contains('candle') && - !seriesType.contains('boxandwhisker') && - !seriesType.contains('waterfall')) { + seriesType != 'boxandwhisker' && + seriesType != 'waterfall') { seriesRenderer._minimumY ??= point.yValue; seriesRenderer._maximumY ??= point.yValue; } @@ -234,8 +238,8 @@ class CategoryAxisRenderer extends ChartAxisRenderer { (!seriesType.contains('range') && !seriesType.contains('hilo') && !seriesType.contains('candle') && - !seriesType.contains('boxandwhisker') && - !seriesType.contains('waterfall'))) { + seriesType != 'boxandwhisker' && + seriesType != 'waterfall')) { seriesRenderer._minimumY = math.min(seriesRenderer._minimumY, point.yValue); seriesRenderer._maximumY = @@ -257,7 +261,7 @@ class CategoryAxisRenderer extends ChartAxisRenderer { _lowMin = _findMinValue(_lowMin ?? point.minimum, point.minimum); _lowMax = _findMaxValue(_lowMax ?? point.minimum, point.minimum); } - if (seriesType.contains('waterfall')) { + if (seriesType == 'waterfall') { /// Empty point is not applicable for Waterfall series. point.yValue ??= 0; seriesRenderer._minimumY = _findMinValue( @@ -271,7 +275,7 @@ class CategoryAxisRenderer extends ChartAxisRenderer { if (seriesType.contains('range') || seriesType.contains('hilo') || seriesType.contains('candle') || - seriesType.contains('boxandwhisker')) { + seriesType == 'boxandwhisker') { _lowMin ??= 0; _lowMax ??= 5; _highMin ??= 0; @@ -308,7 +312,7 @@ class CategoryAxisRenderer extends ChartAxisRenderer { _calculateActualRange(); if (_actualRange != null) { applyRangePadding(_actualRange, _actualRange.interval); - if (type == null && type != 'AxisCross') { + if (type == null && type != 'AxisCross' && _categoryAxis.isVisible) { generateVisibleLabels(); } } @@ -378,7 +382,8 @@ class CategoryAxisRenderer extends ChartAxisRenderer { range.delta = range.maximum - range.minimum; } - if (!(_categoryAxis.minimum != null && _categoryAxis.maximum != null)) { + if (_categoryAxis.isVisible && + !(_categoryAxis.minimum != null && _categoryAxis.maximum != null)) { ///Calculating range padding _applyRangePadding(this, _chartState, range, interval); } @@ -411,7 +416,7 @@ class CategoryAxisRenderer extends ChartAxisRenderer { _zoomPosition = (_visibleRange.minimum - _actualRange.minimum) / range.delta; } - if (_chart.onActualRangeChanged != null) { + if (_categoryAxis.isVisible && _chart.onActualRangeChanged != null) { rangeChangedArgs = ActualRangeChangedArgs(_name, _categoryAxis, range.minimum, range.maximum, range.interval, _orientation); rangeChangedArgs.visibleMin = _visibleRange.minimum; @@ -420,7 +425,11 @@ class CategoryAxisRenderer extends ChartAxisRenderer { _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; } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_axis.dart index 989114d42..b266cf8e2 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_axis.dart @@ -10,80 +10,85 @@ part of charts; /// class DateTimeAxis extends ChartAxis { /// Creating an argument constructor of DateTimeAxis class. - DateTimeAxis( - {String name, - bool isVisible, - AxisTitle title, - AxisLine axisLine, - ChartRangePadding rangePadding, - AxisLabelIntersectAction labelIntersectAction, - ChartDataLabelPosition labelPosition, - TickPosition tickPosition, - EdgeLabelPlacement edgeLabelPlacement, - double zoomFactor, - double zoomPosition, - bool enableAutoIntervalOnZooming, - int labelRotation, - bool isInversed, - bool opposedPosition, - int minorTicksPerInterval, - int maximumLabels, - double plotOffset, - MajorTickLines majorTickLines, - MinorTickLines minorTickLines, - MajorGridLines majorGridLines, - MinorGridLines minorGridLines, - TextStyle labelStyle, - this.dateFormat, - DateTimeIntervalType intervalType, - 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}) - : intervalType = intervalType ?? DateTimeIntervalType.auto, + 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, + DateTimeIntervalType intervalType, + 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, + }) : intervalType = intervalType ?? DateTimeIntervalType.auto, super( - name: name, - isVisible: isVisible, - isInversed: isInversed, - opposedPosition: opposedPosition, - rangePadding: rangePadding, - plotOffset: plotOffset, - labelRotation: labelRotation, - labelIntersectAction: labelIntersectAction, - minorTicksPerInterval: minorTicksPerInterval, - maximumLabels: maximumLabels, - labelStyle: labelStyle, - title: title, - labelAlignment: labelAlignment, - axisLine: axisLine, - majorTickLines: majorTickLines, - minorTickLines: minorTickLines, - majorGridLines: majorGridLines, - minorGridLines: minorGridLines, - edgeLabelPlacement: edgeLabelPlacement, - labelPosition: labelPosition, - tickPosition: tickPosition, - zoomFactor: zoomFactor, - zoomPosition: zoomPosition, - enableAutoIntervalOnZooming: enableAutoIntervalOnZooming, - interactiveTooltip: interactiveTooltip, - interval: interval, - crossesAt: crossesAt, - associatedAxisName: associatedAxisName, - placeLabelsNearAxisLine: placeLabelsNearAxisLine, - plotBands: plotBands, - rangeController: rangeController, - desiredIntervals: desiredIntervals); + 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, + ); ///Formats the date-time axis labels. The default data-time axis label can be formatted ///with various built-in date formats. @@ -213,8 +218,8 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { !seriesType.contains('range') && !seriesType.contains('hilo') && !seriesType.contains('candle') && - !seriesType.contains('boxandwhisker') && - !seriesType.contains('waterfall')) { + seriesType != 'boxandwhisker' && + seriesType != 'waterfall') { seriesRenderer._minimumY ??= point.yValue; seriesRenderer._maximumY ??= point.yValue; } @@ -229,8 +234,8 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { (!seriesType.contains('range') && !seriesType.contains('hilo') && !seriesType.contains('candle') && - !seriesType.contains('boxandwhisker') && - !seriesType.contains('waterfall'))) { + seriesType != 'boxandwhisker' && + seriesType != 'waterfall')) { seriesRenderer._minimumY = math.min(seriesRenderer._minimumY, point.yValue); seriesRenderer._maximumY = @@ -252,7 +257,7 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { _lowMin = _findMinValue(_lowMin ?? point.minimum, point.minimum); _lowMax = _findMaxValue(_lowMax ?? point.minimum, point.minimum); } - if (seriesType.contains('waterfall')) { + if (seriesType == 'waterfall') { /// Empty point is not applicable for Waterfall series. point.yValue ??= 0; seriesRenderer._minimumY = @@ -265,7 +270,7 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { if (seriesType.contains('range') || seriesType.contains('hilo') || seriesType.contains('candle') || - seriesType.contains('boxandwhisker')) { + seriesType == 'boxandwhisker') { _lowMin ??= 0; _lowMax ??= 5; _highMin ??= 0; @@ -303,7 +308,7 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { _calculateActualRange(); if (_actualRange != null) { applyRangePadding(_actualRange, _actualRange.interval); - if (type == null && type != 'AxisCross') { + if (type == null && type != 'AxisCross' && _dateTimeAxis.isVisible) { generateVisibleLabels(); } } @@ -765,7 +770,9 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { _min = range.minimum.toInt(); _max = range.maximum.toInt(); ActualRangeChangedArgs rangeChangedArgs; - if (_dateTimeAxis.minimum == null && _dateTimeAxis.maximum == null) { + if (_dateTimeAxis.isVisible && + _dateTimeAxis.minimum == null && + _dateTimeAxis.maximum == null) { final ChartRangePadding rangePadding = _calculateRangePadding(this, _chart); final DateTime minimum = DateTime.fromMillisecondsSinceEpoch(_min); @@ -826,7 +833,7 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { _zoomPosition = (_visibleRange.minimum - _actualRange.minimum) / range.delta; } - if (_chart.onActualRangeChanged != null) { + if (_dateTimeAxis.isVisible && _chart.onActualRangeChanged != null) { rangeChangedArgs = ActualRangeChangedArgs(_name, _dateTimeAxis, range.minimum, range.maximum, range.interval, _orientation); rangeChangedArgs.visibleMin = _visibleRange.minimum; @@ -839,7 +846,11 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { _visibleRange.maximum = rangeChangedArgs.visibleMax is DateTime ? rangeChangedArgs.visibleMax.millisecondsSinceEpoch : rangeChangedArgs.visibleMax; + _visibleRange.delta = _visibleRange.maximum - _visibleRange.minimum; _visibleRange.interval = rangeChangedArgs.visibleInterval; + _zoomFactor = _visibleRange.delta / (range.delta); + _zoomPosition = + (_visibleRange.minimum - _actualRange.minimum) / range.delta; } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/logarithmic_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/logarithmic_axis.dart index 2f69cb6a2..eb691f0f2 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/logarithmic_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/logarithmic_axis.dart @@ -8,80 +8,85 @@ part of charts; /// _Note:_ This is only applicable for [SfCartesianChart]. 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, - double logBase, - this.labelFormat, - this.numberFormat, - this.visibleMinimum, - this.visibleMaximum, - LabelAlignment labelAlignment, - dynamic crossesAt, - String associatedAxisName, - bool placeLabelsNearAxisLine, - List plotBands, - int desiredIntervals, - RangeController rangeController}) - : logBase = logBase ?? 10, + 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, + double logBase, + this.labelFormat, + this.numberFormat, + this.visibleMinimum, + this.visibleMaximum, + LabelAlignment labelAlignment, + dynamic crossesAt, + String associatedAxisName, + bool placeLabelsNearAxisLine, + List plotBands, + int desiredIntervals, + RangeController rangeController, + double maximumLabelWidth, + double labelsExtent, + }) : logBase = logBase ?? 10, super( - name: name, - isVisible: isVisible, - anchorRangeToVisiblePoints: anchorRangeToVisiblePoints, - isInversed: isInversed, - opposedPosition: opposedPosition, - labelRotation: labelRotation, - labelIntersectAction: labelIntersectAction, - labelPosition: labelPosition, - tickPosition: tickPosition, - minorTicksPerInterval: minorTicksPerInterval, - maximumLabels: maximumLabels, - labelStyle: labelStyle, - title: title, - axisLine: axisLine, - edgeLabelPlacement: edgeLabelPlacement, - labelAlignment: labelAlignment, - majorTickLines: majorTickLines, - minorTickLines: minorTickLines, - majorGridLines: majorGridLines, - minorGridLines: minorGridLines, - plotOffset: plotOffset, - zoomFactor: zoomFactor, - zoomPosition: zoomPosition, - enableAutoIntervalOnZooming: enableAutoIntervalOnZooming, - interactiveTooltip: interactiveTooltip, - interval: interval, - crossesAt: crossesAt, - associatedAxisName: associatedAxisName, - placeLabelsNearAxisLine: placeLabelsNearAxisLine, - plotBands: plotBands, - desiredIntervals: desiredIntervals, - rangeController: rangeController); + 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, + ); ///Formats the numeric axis labels. /// @@ -303,7 +308,7 @@ class LogarithmicAxisRenderer extends ChartAxisRenderer { _zoomPosition = (_visibleRange.minimum - _actualRange.minimum) / _actualRange.delta; } - if (type == null && type != 'AxisCross') { + if (type == null && type != 'AxisCross' && _logarithmicAxis.isVisible) { generateVisibleLabels(); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/numeric_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/numeric_axis.dart index fbe25c078..b19f0a535 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/numeric_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/numeric_axis.dart @@ -50,6 +50,8 @@ class NumericAxis extends ChartAxis { int decimalPlaces, int desiredIntervals, RangeController rangeController, + double maximumLabelWidth, + double labelsExtent, }) : decimalPlaces = decimalPlaces ?? 3, super( name: name, @@ -84,6 +86,8 @@ class NumericAxis extends ChartAxis { plotBands: plotBands, desiredIntervals: desiredIntervals, rangeController: rangeController, + maximumLabelWidth: maximumLabelWidth, + labelsExtent: labelsExtent, ); ///Formats the numeric axis labels. @@ -226,8 +230,8 @@ class NumericAxisRenderer extends ChartAxisRenderer { !seriesType.contains('range') && !seriesType.contains('hilo') && !seriesType.contains('candle') && - !seriesType.contains('boxandwhisker') && - !seriesType.contains('waterfall')) { + seriesType != 'boxandwhisker' && + seriesType != 'waterfall') { seriesRenderer._minimumY ??= point.yValue; seriesRenderer._maximumY ??= point.yValue; } @@ -243,8 +247,8 @@ class NumericAxisRenderer extends ChartAxisRenderer { (!seriesType.contains('range') && !seriesType.contains('hilo') && !seriesType.contains('candle') && - !seriesType.contains('boxandwhisker') && - !seriesType.contains('waterfall'))) { + seriesType != 'boxandwhisker' && + seriesType != 'waterfall')) { seriesRenderer._minimumY = math.min(seriesRenderer._minimumY, point.yValue); seriesRenderer._maximumY = @@ -266,7 +270,7 @@ class NumericAxisRenderer extends ChartAxisRenderer { _lowMin = _findMinValue(_lowMin ?? point.minimum, point.minimum); _lowMax = _findMaxValue(_lowMax ?? point.minimum, point.minimum); } - if (seriesType.contains('waterfall')) { + if (seriesType == 'waterfall') { /// Empty point is not applicable for Waterfall series. point.yValue ??= 0; seriesRenderer._minimumY = _findMinValue( @@ -279,7 +283,7 @@ class NumericAxisRenderer extends ChartAxisRenderer { if (seriesType.contains('range') || seriesType.contains('hilo') || seriesType.contains('candle') || - seriesType.contains('boxandwhisker')) { + seriesType == 'boxandwhisker') { _lowMin ??= 0; _lowMax ??= 5; _highMin ??= 0; @@ -320,7 +324,7 @@ class NumericAxisRenderer extends ChartAxisRenderer { _calculateActualRange(); if (_actualRange != null) { applyRangePadding(_actualRange, _actualRange.interval); - if (type == null && type != 'AxisCross') { + if (type == null && type != 'AxisCross' && _numericAxis.isVisible) { generateVisibleLabels(); } } @@ -364,7 +368,8 @@ class NumericAxisRenderer extends ChartAxisRenderer { @override void applyRangePadding(_VisibleRange range, num interval) { ActualRangeChangedArgs rangeChangedArgs; - if (!(_numericAxis.minimum != null && _numericAxis.maximum != null)) { + if (_numericAxis.isVisible && + !(_numericAxis.minimum != null && _numericAxis.maximum != null)) { ///Calculating range padding _applyRangePadding(this, _chartState, range, interval); } @@ -390,7 +395,7 @@ class NumericAxisRenderer extends ChartAxisRenderer { _zoomPosition = (_visibleRange.minimum - _actualRange.minimum) / range.delta; } - if (_chart.onActualRangeChanged != null) { + if (_numericAxis.isVisible && _chart.onActualRangeChanged != null) { rangeChangedArgs = ActualRangeChangedArgs(_name, _numericAxis, range.minimum, range.maximum, range.interval, _orientation); rangeChangedArgs.visibleMin = _visibleRange.minimum; @@ -399,7 +404,11 @@ class NumericAxisRenderer extends ChartAxisRenderer { _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; } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/base/chart_base.dart b/packages/syncfusion_flutter_charts/lib/src/chart/base/chart_base.dart index bb2b38f17..fb99faf73 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/base/chart_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/base/chart_base.dart @@ -705,7 +705,7 @@ class SfCartesianChart extends StatefulWidget { /// { /// if(args.index==1) /// { - /// args.indicatorname="changed1"; + /// args.indicatorName="changed1"; /// args.signalLineColor=Colors.green; /// args.signalLineWidth=10.0; /// } @@ -1026,9 +1026,11 @@ class SfCartesianChartState extends State List _dataLabelTemplateRegions; List _annotationRegions; bool _animateCompleted; + bool _legendRefresh = false; bool _widgetNeedUpdate; _DataLabelRenderer _renderDataLabel; - _CartesianAxisRenderer _renderAxis; + _CartesianAxisRenderer _renderOutsideAxis; + _CartesianAxisRenderer _renderInsideAxis; List _oldSeriesRenderers; List> _oldSeriesKeys; bool _isLegendToggled; @@ -1053,10 +1055,6 @@ class SfCartesianChartState extends State bool _isRangeSelectionSlider = false; bool _isSeriesLoaded; bool _isNeedUpdate; - //ignore: prefer_final_fields - bool _trackballWithoutTouch = true; - //ignore: prefer_final_fields - bool _crosshairWithoutTouch = true; List _seriesRenderers; /// Holds the information of AxisBase class @@ -1075,6 +1073,10 @@ class SfCartesianChartState extends State /// Whether to check chart axis is inverted or not bool _requireInvertedAxis; + /// To check if axis trimmed text is tapped + //ignore: prefer_final_fields + bool _requireAxisTooltip = false; + //ignore: prefer_final_fields List<_ChartPointInfo> _chartPointInfo = <_ChartPointInfo>[]; @@ -1108,6 +1110,10 @@ class SfCartesianChartState extends State //holds the no of animation completed series int _animationCompleteCount; + SelectionArgs _selectionArgs; + + bool _isTouchUp = false; + /// To intialize default values void _initializeDefaultValues() { _chartAxis = _ChartAxis(this); @@ -1373,15 +1379,28 @@ class SfCartesianChartState extends State series.selectionBehavior.enable || series.selectionSettings.enable; seriesRenderer._visible = null; seriesRenderer._chart = widget; + seriesRenderer._hasDataLabelTemplate = false; if (oldWidgetSeriesRenderers != null && oldSeriesIndex != null && oldWidgetSeriesRenderers.length > oldSeriesIndex) { seriesRenderer._oldSeries = oldWidgetSeriesRenderers[oldSeriesIndex]._series; - seriesRenderer._oldDataPoints = >[] - //ignore: prefer_spread_collections - ..addAll(oldWidgetSeriesRenderers[oldSeriesIndex]._dataPoints); + if (seriesRenderer is FastLineSeriesRenderer && + oldWidgetSeriesRenderers[oldSeriesIndex] + is FastLineSeriesRenderer) { + final FastLineSeriesRenderer fastlineSeriesRenderer = + oldWidgetSeriesRenderers[oldSeriesIndex]; + seriesRenderer._oldDataPoints = >[] + //ignore: prefer_spread_collections + ..addAll(fastlineSeriesRenderer._overallDataPoints); + } else { + seriesRenderer._oldDataPoints = >[] + //ignore: prefer_spread_collections + ..addAll(oldWidgetSeriesRenderers[oldSeriesIndex]._dataPoints); + } + seriesRenderer._oldSelectedIndexes = + oldWidgetSeriesRenderers[oldSeriesIndex]._oldSelectedIndexes; seriesRenderer._repaintNotifier = oldWidgetSeriesRenderers[oldSeriesIndex]._repaintNotifier; seriesRenderer._animationController = @@ -1423,6 +1442,7 @@ class SfCartesianChartState extends State final RenderBox renderBox = templateContext.context.findRenderObject(); templateContext.size = renderBox.size; } + _legendRefresh = true; setState(() { /// The chart will be rebuilding again, Once legend template sizes will be calculated. }); @@ -1463,6 +1483,7 @@ class SfCartesianChartState extends State } } } + _widgetNeedUpdate = false; if (mounted) { @@ -1571,7 +1592,7 @@ class SfCartesianChartState extends State /// 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(widget); + _calculateImage(this); } _deviceOrientation = MediaQuery.of(context).orientation; return Expanded( @@ -1712,6 +1733,16 @@ class SfCartesianChartState extends State final int index = visibleSeriesRenderers.length - 1; final String legendItemText = visibleSeriesRenderers[index]._series.legendItemText; + final String legendText = _chart.legend.legendItemBuilder != null + ? visibleSeriesRenderers[index]._seriesName + : _chartSeries._chartState._chartLegend?.legendCollections != + null && + _chartSeries + ._chartState._chartLegend.legendCollections.isNotEmpty + ? _chartSeries + ._chartState._chartLegend?.legendCollections[index]?.text + : null; + final String seriesName = visibleSeriesRenderers[index]._series.name; _chartSeries.visibleSeriesRenderers[visibleSeriesRenderers.length - 1] ._visible = @@ -1719,7 +1750,10 @@ class SfCartesianChartState extends State visibleSeriesRenderers.length - 1, visibleSeriesRenderers[visibleSeriesRenderers.length - 1] ._series, - legendItemText ?? seriesName ?? 'Series $index'); + legendText ?? + legendItemText ?? + seriesName ?? + 'Series $index'); } final CartesianSeriesRenderer cSeriesRenderer = _chartSeries .visibleSeriesRenderers[visibleSeriesRenderers.length - 1] @@ -1879,6 +1913,15 @@ class _ContainerArea extends StatelessWidget { XyDataSeriesRenderer _seriesRenderer; @override Widget build(BuildContext context) { + final bool isUserInteractionEnabled = + chart.zoomPanBehavior.enableDoubleTapZooming || + chart.zoomPanBehavior.enableMouseWheelZooming || + chart.zoomPanBehavior.enableSelectionZooming || + chart.zoomPanBehavior.enablePanning || + chart.zoomPanBehavior.enablePinching || + chart.trackballBehavior.enable || + chart.crosshairBehavior.enable || + chart.onChartTouchInteractionMove != null; return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Container( @@ -1886,9 +1929,127 @@ class _ContainerArea extends StatelessWidget { /// To get the mouse region of the chart child: MouseRegion( - child: _initializeChart(constraints, context), onHover: (PointerEvent event) => _performMouseHover(event), - onExit: (PointerEvent event) => _performMouseExit(event))); + onExit: (PointerEvent event) => _performMouseExit(event), + child: Listener( + onPointerDown: (PointerDownEvent event) { + _performPointerDown(event); + ChartTouchInteractionArgs touchArgs; + if (chart.onChartTouchInteractionDown != null) { + touchArgs = ChartTouchInteractionArgs(); + touchArgs.position = + renderBox.globalToLocal(event.position); + chart.onChartTouchInteractionDown(touchArgs); + } + }, + onPointerMove: (PointerMoveEvent event) { + _performPointerMove(event); + ChartTouchInteractionArgs touchArgs; + if (chart.onChartTouchInteractionMove != null) { + touchArgs = ChartTouchInteractionArgs(); + touchArgs.position = + renderBox.globalToLocal(event.position); + chart.onChartTouchInteractionMove(touchArgs); + } + }, + onPointerUp: (PointerUpEvent event) { + _chartState._isTouchUp = true; + _performPointerUp(event); + _chartState._isTouchUp = false; + ChartTouchInteractionArgs touchArgs; + if (chart.onChartTouchInteractionUp != null) { + touchArgs = ChartTouchInteractionArgs(); + touchArgs.position = + renderBox.globalToLocal(event.position); + chart.onChartTouchInteractionUp(touchArgs); + } + }, + onPointerSignal: (PointerSignalEvent event) { + if (event is PointerScrollEvent) { + _performPointerSignal(event); + } + }, + child: GestureDetector( + onTapDown: (TapDownDetails details) { + final Offset position = + renderBox.globalToLocal(details.globalPosition); + _touchPosition = position; + }, + onTapUp: (TapUpDetails details) { + final Offset position = + renderBox.globalToLocal(details.globalPosition); + final List + visibleSeriesRenderer = + _chartState._chartSeries.visibleSeriesRenderers; + if (chart.onPointTapped != null) { + _calculatePointSeriesIndex(chart, position); + } + if (chart.onAxisLabelTapped != null) { + _triggerAxisLabelEvent(position); + } + if (chart.onDataLabelTapped != null) { + _triggerDataLabelEvent( + chart, visibleSeriesRenderer, position); + } + }, + onDoubleTap: () { + _performDoubleTap(); + }, + onLongPressMoveUpdate: + (LongPressMoveUpdateDetails details) { + _performLongPressMoveUpdate(details); + }, + onLongPress: () { + _performLongPress(); + }, + onLongPressEnd: (LongPressEndDetails details) { + _performLongPressEnd(); + }, + // onPanUpdate: (DragUpdateDetails details) { + // _performPanUpdate(details); + // }, + // onPanEnd: (DragEndDetails details) { + // _performPanEnd(details); + // }, + // onPanDown: (DragDownDetails details) { + // _performPanDown(details); + // }, + onVerticalDragUpdate: isUserInteractionEnabled + ? (DragUpdateDetails details) { + _performPanUpdate(details); + } + : null, + onVerticalDragEnd: isUserInteractionEnabled + ? (DragEndDetails details) { + _performPanEnd(details); + } + : null, + onVerticalDragDown: isUserInteractionEnabled + ? (DragDownDetails details) { + _performPanDown(details); + } + : null, + onHorizontalDragUpdate: isUserInteractionEnabled + ? (DragUpdateDetails details) { + _performPanUpdate(details); + } + : null, + onHorizontalDragEnd: isUserInteractionEnabled + ? (DragEndDetails details) { + _performPanEnd(details); + } + : null, + onHorizontalDragDown: isUserInteractionEnabled + ? (DragDownDetails details) { + _performPanDown(details); + } + : null, + child: Container( + child: _initializeChart(constraints, context), + height: constraints.maxHeight, + width: constraints.maxWidth, + decoration: const BoxDecoration( + color: Colors.transparent)))))); }); } @@ -1950,6 +2111,7 @@ class _ContainerArea extends StatelessWidget { _bindInteractionWidgets(constraints, context); renderBox = context.findRenderObject(); _chartState._containerArea = this; + _chartState._legendRefresh = false; return Container(child: Stack(children: _chartWidgets)); } @@ -2065,6 +2227,7 @@ class _ContainerArea extends StatelessWidget { final CartesianSeriesRenderer seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; final XyDataSeries series = seriesRenderer._series; + num padding; if (series.dataLabelSettings.isVisible && seriesRenderer._visible) { for (int j = 0; j < seriesRenderer._dataPoints.length; j++) { point = seriesRenderer._dataPoints[j]; @@ -2078,40 +2241,35 @@ class _ContainerArea extends StatelessWidget { i) : null; if (labelWidget != null) { - final _ChartLocation location = _calculatePoint( - point.xValue, - seriesRenderer._seriesType.contains('range') - ? point.high - : seriesRenderer._seriesType.contains('boxandwhisker') - ? point.minimum - : point.yValue, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - _chartState._requireInvertedAxis, - series, - _chartState._chartAxis._axisClipRect); - final _ChartTemplateInfo templateInfo = _ChartTemplateInfo( - key: GlobalKey(), - templateType: 'DataLabel', - pointIndex: j, - seriesIndex: i, - needMeasure: true, - clipRect: _chartState._chartAxis._axisClipRect, - animationDuration: - (series.animationDuration + 1000.0).floor(), - widget: labelWidget, - location: Offset(location.x, location.y)); - _chartState._templates.add(templateInfo); - if (seriesRenderer._seriesType.contains('range')) { - final _ChartLocation rangeLocation = _calculatePoint( + final String seriesType = seriesRenderer._seriesType; + final List dataLabelTemplateYValues = + (seriesType.contains('range') || + (seriesType.contains('hilo') && + !seriesType.contains('hiloopenclose'))) + ? [point.low, point.high] + : (seriesType.contains('candle') || + seriesType.contains('hiloopenclose')) + ? [point.low, point.high, point.open, point.close] + : seriesType.contains('box') + ? [point.minimum] + : [point.y]; + + for (int k = 0; k < dataLabelTemplateYValues.length; k++) { + padding = (k == 0 && + dataLabelTemplateYValues.length > 1 && + !_chartState._requireInvertedAxis) + ? 20 + : 0; + final _ChartLocation location = _calculatePoint( point.xValue, - point.low, + dataLabelTemplateYValues[k], seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, _chartState._requireInvertedAxis, series, _chartState._chartAxis._axisClipRect); - final _ChartTemplateInfo templateInfo2 = _ChartTemplateInfo( + + final _ChartTemplateInfo templateInfo = _ChartTemplateInfo( key: GlobalKey(), templateType: 'DataLabel', pointIndex: j, @@ -2121,8 +2279,8 @@ class _ContainerArea extends StatelessWidget { animationDuration: (series.animationDuration + 1000.0).floor(), widget: labelWidget, - location: Offset(rangeLocation.x, rangeLocation.y)); - _chartState._templates.add(templateInfo2); + location: Offset(location.x, location.y + padding)); + _chartState._templates.add(templateInfo); } } } @@ -2140,6 +2298,7 @@ class _ContainerArea extends StatelessWidget { i < _chartState._chartSeries.visibleSeriesRenderers.length; i++) { _seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; + _seriesRenderer._animationCompleted = false; _series = _seriesRenderer._series; final String _seriesType = _seriesRenderer._seriesType; if (_seriesRenderer != null && _seriesRenderer._visible) { @@ -2169,22 +2328,45 @@ class _ContainerArea extends StatelessWidget { _chartState._selectedSegments; selectionBehaviorRenderer._selectionRenderer.unselectedSegments = _chartState._unselectedSegments; + //To determine whether initialSelectedDataIndexes collection is updated dynamically + bool isSelecetedIndexesUpdated = false; + if (_series.initialSelectedDataIndexes != null && + _series.initialSelectedDataIndexes.isNotEmpty && + _seriesRenderer._oldSelectedIndexes != null && + _seriesRenderer._oldSelectedIndexes.isNotEmpty && + _seriesRenderer._oldSelectedIndexes.length == + _series.initialSelectedDataIndexes.length) { + for (final int index in _series.initialSelectedDataIndexes) { + isSelecetedIndexesUpdated = + !_seriesRenderer._oldSelectedIndexes.contains(index); + if (isSelecetedIndexesUpdated) { + break; + } + } + } else { + isSelecetedIndexesUpdated = + _series.initialSelectedDataIndexes.isNotEmpty; + } if (_chartState._isRangeSelectionSlider == false && selectionBehavior.enable && - _series.initialSelectedDataIndexes.isNotEmpty) { - for (int j = 0; - j < _seriesRenderer._series.initialSelectedDataIndexes.length; - j++) { + isSelecetedIndexesUpdated) { + for (int j = 0; j < _seriesRenderer._dataPoints.length; j++) { final ChartSegment segment = ColumnSegment(); - segment.currentSegmentIndex = - _seriesRenderer._series.initialSelectedDataIndexes[j]; + segment.currentSegmentIndex = j; segment._seriesIndex = i; - if (_seriesRenderer._series.initialSelectedDataIndexes + segment._currentPoint = _seriesRenderer._dataPoints[j]; + if (_series.initialSelectedDataIndexes .contains(segment.currentSegmentIndex)) { selectionBehaviorRenderer._selectionRenderer.selectedSegments .add(segment); + } else { + selectionBehaviorRenderer._selectionRenderer.unselectedSegments + .add(segment); } } + _seriesRenderer._oldSelectedIndexes = [] + //ignore: prefer_spread_collections + ..addAll(_series.initialSelectedDataIndexes); } } if (_seriesRenderer._isIndicator) { @@ -2203,9 +2385,11 @@ class _ContainerArea extends StatelessWidget { if (_seriesRenderer._animationController != null && _series.animationDuration > 0 && (_chartState._oldDeviceOrientation == null || + _chartState._legendRefresh || _chartState._oldDeviceOrientation == _chartState._deviceOrientation) && (_chartState._initialRender || + _chartState._legendRefresh || ((_seriesType == 'column' || _seriesType == 'bar') && _chartState._legendToggling) || (!_chartState._legendToggling && @@ -2230,6 +2414,7 @@ class _ContainerArea extends StatelessWidget { curve: const Interval(1.0, 1.0, curve: Curves.decelerate), )); _chartState._animationCompleteCount++; + _seriesRenderer._animationCompleted = true; _setAnimationStatus(_chartState); } } @@ -2256,9 +2441,14 @@ class _ContainerArea extends StatelessWidget { if (_chartState._chartAxis._axisRenderersCollection != null && _chartState._chartAxis._axisRenderersCollection.isNotEmpty && _chartState._chartAxis._axisRenderersCollection.length > 1) { - _chartState._renderAxis = _CartesianAxisRenderer( + final Widget axisWidget = _CartesianAxisRenderer( chartState: _chartState, renderType: renderType); - _chartWidgets.add(_chartState._renderAxis); + if (renderType == 'outside') { + _chartState._renderOutsideAxis = axisWidget; + } else { + _chartState._renderInsideAxis = axisWidget; + } + _chartWidgets.add(axisWidget); } } @@ -2281,6 +2471,7 @@ class _ContainerArea extends StatelessWidget { _seriesType.contains('stackedcolumn') || _seriesType.contains('stackedbar') || _seriesType.contains('range') || + _seriesType == 'histogram' || _seriesType == 'waterfall') { for (int j = 0; j < seriesRenderer._dataPoints.length; j++) { if (seriesRenderer._dataPoints[j].region != null && @@ -2366,8 +2557,13 @@ class _ContainerArea extends StatelessWidget { if (chart.trackballBehavior != null && chart.trackballBehavior.enable && chart.trackballBehavior.activationMode == ActivationMode.singleTap) { - _chartState._trackballBehaviorRenderer - .onTouchDown(position.dx, position.dy); + if (chart.trackballBehavior.builder != null) { + _chartState._trackballBehaviorRenderer._isMoving = true; + _chartState._trackballBehaviorRenderer._showTemplateTrackball(position); + } else { + _chartState._trackballBehaviorRenderer + .onTouchDown(position.dx, position.dy); + } } if (chart.crosshairBehavior != null && chart.crosshairBehavior.enable && @@ -2433,15 +2629,10 @@ class _ContainerArea extends StatelessWidget { chart.zoomPanBehavior.enablePinching || chart.zoomPanBehavior.enableSelectionZooming) && !chart.trackballBehavior.shouldAlwaysShow))) { - ChartTouchInteractionArgs touchUpArgs; _chartState._trackballBehaviorRenderer .onTouchUp(position.dx, position.dy); + _chartState._trackballBehaviorRenderer._isLongPressActivated = false; - if (chart.onChartTouchInteractionUp != null) { - touchUpArgs = ChartTouchInteractionArgs(); - touchUpArgs.position = position; - chart.onChartTouchInteractionUp(touchUpArgs); - } } if ((chart.crosshairBehavior != null && chart.crosshairBehavior.enable && @@ -2455,18 +2646,13 @@ class _ContainerArea extends StatelessWidget { chart.zoomPanBehavior.enablePinching || chart.zoomPanBehavior.enableSelectionZooming) && !chart.crosshairBehavior.shouldAlwaysShow))) { - ChartTouchInteractionArgs touchUpArgs; _chartState._crosshairBehaviorRenderer .onTouchUp(position.dx, position.dy); _chartState._crosshairBehaviorRenderer._isLongPressActivated = true; - if (chart.onChartTouchInteractionUp != null) { - touchUpArgs = ChartTouchInteractionArgs(); - touchUpArgs.position = position; - chart.onChartTouchInteractionUp(touchUpArgs); - } } if (chart.tooltipBehavior.enable && - chart.tooltipBehavior.activationMode == ActivationMode.singleTap) { + chart.tooltipBehavior.activationMode == ActivationMode.singleTap || + _shouldShowAxisTooltip(_chartState)) { _chartState._tooltipBehaviorRenderer._isInteraction = true; if (chart.tooltipBehavior.builder != null) { _chartState._tooltipBehaviorRenderer._showTemplateTooltip(position); @@ -2552,13 +2738,23 @@ class _ContainerArea extends StatelessWidget { chart.trackballBehavior.activationMode != ActivationMode.doubleTap && position != null) { if (chart.trackballBehavior.activationMode == ActivationMode.singleTap) { - _chartState._trackballBehaviorRenderer - .onTouchMove(position.dx, position.dy); - } else if (chart.trackballBehavior.activationMode == - ActivationMode.longPress && + if (chart.trackballBehavior.builder != null) { + _chartState._trackballBehaviorRenderer + ._showTemplateTrackball(position); + } else { + _chartState._trackballBehaviorRenderer + .onTouchMove(position.dx, position.dy); + } + } + if (chart.trackballBehavior.activationMode == ActivationMode.longPress && _chartState._trackballBehaviorRenderer._isLongPressActivated) { - _chartState._trackballBehaviorRenderer - .onTouchMove(position.dx, position.dy); + if (chart.trackballBehavior.builder != null) { + _chartState._trackballBehaviorRenderer + ._showTemplateTrackball(position); + } else { + _chartState._trackballBehaviorRenderer + .onTouchMove(position.dx, position.dy); + } } } if (chart.crosshairBehavior != null && @@ -2612,7 +2808,9 @@ class _ContainerArea extends StatelessWidget { if (_tapDownDetails != null) { position = renderBox.globalToLocal(_tapDownDetails); if (chart.tooltipBehavior.enable && - chart.tooltipBehavior.activationMode == ActivationMode.longPress) { + chart.tooltipBehavior.activationMode == + ActivationMode.longPress || + _shouldShowAxisTooltip(_chartState)) { _chartState._tooltipBehaviorRenderer._isInteraction = true; if (chart.tooltipBehavior.builder != null) { _chartState._tooltipBehaviorRenderer._showTemplateTooltip(position); @@ -2639,8 +2837,12 @@ class _ContainerArea extends StatelessWidget { ActivationMode.longPress) && _chartState._zoomPanBehaviorRenderer._isPinching != true) { _chartState._trackballBehaviorRenderer._isLongPressActivated = true; - _chartState._trackballBehaviorRenderer - .onTouchDown(position.dx, position.dy); + if (chart.trackballBehavior.builder != null) { + _chartState._trackballBehaviorRenderer._showTemplateTrackball(position); + } else { + _chartState._trackballBehaviorRenderer + .onTouchDown(position.dx, position.dy); + } } if ((chart.crosshairBehavior != null && chart.crosshairBehavior.enable == true && @@ -2662,11 +2864,18 @@ class _ContainerArea extends StatelessWidget { if (chart.trackballBehavior != null && chart.trackballBehavior.enable && chart.trackballBehavior.activationMode == ActivationMode.doubleTap) { - _chartState._trackballBehaviorRenderer - .onDoubleTap(position.dx, position.dy); + if (chart.trackballBehavior.builder != null) { + _chartState._trackballBehaviorRenderer + ._showTemplateTrackball(position); + } else { + _chartState._trackballBehaviorRenderer + .onDoubleTap(position.dx, position.dy); + } _chartState._enableDoubleTap = true; + _chartState._trackballBehaviorRenderer .onTouchUp(position.dx, position.dy); + _chartState._enableDoubleTap = false; } if (chart.crosshairBehavior != null && @@ -2680,7 +2889,9 @@ class _ContainerArea extends StatelessWidget { _chartState._enableDoubleTap = false; } if (chart.tooltipBehavior.enable && - chart.tooltipBehavior.activationMode == ActivationMode.doubleTap) { + chart.tooltipBehavior.activationMode == + ActivationMode.doubleTap || + _shouldShowAxisTooltip(_chartState)) { _chartState._tooltipBehaviorRenderer._isInteraction = true; if (chart.tooltipBehavior.builder != null) { _chartState._tooltipBehaviorRenderer._showTemplateTooltip(position); @@ -2731,14 +2942,25 @@ class _ContainerArea extends StatelessWidget { !panInProgress && chart.trackballBehavior.activationMode != ActivationMode.doubleTap) { if (chart.trackballBehavior.activationMode == ActivationMode.singleTap) { - _chartState._trackballBehaviorRenderer - .onTouchMove(position.dx, position.dy); + if (chart.trackballBehavior.builder != null) { + _chartState._trackballBehaviorRenderer._isMoving = true; + _chartState._trackballBehaviorRenderer + ._showTemplateTrackball(position); + } else { + _chartState._trackballBehaviorRenderer + .onTouchMove(position.dx, position.dy); + } } else if (chart.trackballBehavior != null && chart.trackballBehavior.activationMode == ActivationMode.longPress && _chartState._trackballBehaviorRenderer._isLongPressActivated == true) { - _chartState._trackballBehaviorRenderer - .onTouchMove(position.dx, position.dy); + if (chart.trackballBehavior.builder != null) { + _chartState._trackballBehaviorRenderer + ._showTemplateTrackball(position); + } else { + _chartState._trackballBehaviorRenderer + .onTouchMove(position.dx, position.dy); + } } } if (chart.crosshairBehavior != null && @@ -2785,7 +3007,8 @@ class _ContainerArea extends StatelessWidget { _chartState._tooltipBehaviorRenderer._isHovering = true; _chartState._tooltipBehaviorRenderer._isInteraction = true; final Offset position = renderBox.globalToLocal(event.position); - if (chart.tooltipBehavior.enable) { + _shouldShowAxisTooltip(_chartState); + if (chart.tooltipBehavior.enable || _shouldShowAxisTooltip(_chartState)) { if (chart.tooltipBehavior.builder != null) { _chartState._tooltipBehaviorRenderer._showTemplateTooltip(position); } else { @@ -2793,7 +3016,12 @@ class _ContainerArea extends StatelessWidget { } } if (chart.trackballBehavior.enable) { - _chartState._trackballBehaviorRenderer.onEnter(position.dx, position.dy); + if (chart.trackballBehavior.builder != null) { + _chartState._trackballBehaviorRenderer._showTemplateTrackball(position); + } else { + _chartState._trackballBehaviorRenderer + .onEnter(position.dx, position.dy); + } } if (chart.crosshairBehavior.enable) { _chartState._crosshairBehaviorRenderer.onEnter(position.dx, position.dy); @@ -2804,7 +3032,7 @@ class _ContainerArea extends StatelessWidget { void _performMouseExit(PointerEvent event) { _chartState._tooltipBehaviorRenderer._isHovering = false; final Offset position = renderBox.globalToLocal(event.position); - if (chart.tooltipBehavior.enable) { + if (chart.tooltipBehavior.enable || _shouldShowAxisTooltip(_chartState)) { _chartState._tooltipBehaviorRenderer.onExit(position.dx, position.dy); } if (chart.crosshairBehavior.enable) { @@ -2818,9 +3046,10 @@ class _ContainerArea extends StatelessWidget { /// To bind the interaction widgets void _bindInteractionWidgets( BoxConstraints constraints, BuildContext context) { - final RenderBox renderBox = context.findRenderObject(); _TrackballPainter trackballPainter; _CrosshairPainter crosshairPainter; + + final List userInteractionWidgets = []; final _ZoomRectPainter zoomRectPainter = _ZoomRectPainter(chartState: _chartState); _chartState._zoomPanBehaviorRenderer._painter = zoomRectPainter; @@ -2828,16 +3057,26 @@ class _ContainerArea extends StatelessWidget { chart.zoomPanBehavior._chartState = chart.crosshairBehavior._chartState = _chartState; if (chart.trackballBehavior != null && chart.trackballBehavior.enable) { - trackballPainter = _TrackballPainter( - chartState: _chartState, - valueNotifier: _chartState._trackballRepaintNotifier); - _chartState._trackballBehaviorRenderer._trackballPainter = - trackballPainter; - _chartWidgets.add(Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - decoration: const BoxDecoration(color: Colors.transparent), - child: CustomPaint(painter: trackballPainter))); + if (chart.trackballBehavior.builder != null) { + _chartState._trackballBehaviorRenderer._trackballTemplate = + _TrackballTemplate( + key: GlobalKey>(), + trackballBehavior: chart.trackballBehavior, + chartState: _chartState); + userInteractionWidgets + .add(_chartState._trackballBehaviorRenderer._trackballTemplate); + } else { + trackballPainter = _TrackballPainter( + chartState: _chartState, + valueNotifier: _chartState._trackballRepaintNotifier); + _chartState._trackballBehaviorRenderer._trackballPainter = + trackballPainter; + userInteractionWidgets.add(Container( + height: constraints.maxHeight, + width: constraints.maxWidth, + decoration: const BoxDecoration(color: Colors.transparent), + child: CustomPaint(painter: trackballPainter))); + } } if (chart.crosshairBehavior != null && chart.crosshairBehavior.enable) { crosshairPainter = _CrosshairPainter( @@ -2845,14 +3084,13 @@ class _ContainerArea extends StatelessWidget { valueNotifier: _chartState._crosshairRepaintNotifier); _chartState._crosshairBehaviorRenderer._crosshairPainter = crosshairPainter; - _chartWidgets.add(Container( + userInteractionWidgets.add(Container( height: constraints.maxHeight, width: constraints.maxWidth, decoration: const BoxDecoration(color: Colors.transparent), child: CustomPaint(painter: crosshairPainter))); } - _chartWidgets.add(_getListener(renderBox, constraints)); - if (chart.tooltipBehavior.enable) { + if (chart.tooltipBehavior.enable || _shouldShowAxisTooltip(_chartState)) { if (chart.tooltipBehavior.builder != null) { _chartState._tooltipBehaviorRenderer._tooltipTemplate = _TooltipTemplate( @@ -2861,136 +3099,19 @@ class _ContainerArea extends StatelessWidget { duration: chart.tooltipBehavior.duration, tooltipBehavior: chart.tooltipBehavior, chartState: _chartState); - _chartWidgets + userInteractionWidgets .add(_chartState._tooltipBehaviorRenderer._tooltipTemplate); } else { _chartState._tooltipBehaviorRenderer._chartTooltip = _ChartTooltipRenderer(chartState: _chartState); - _chartWidgets.add(_chartState._tooltipBehaviorRenderer._chartTooltip); + userInteractionWidgets + .add(_chartState._tooltipBehaviorRenderer._chartTooltip); } } - } - - /// Listener method for all events - Widget _getListener(RenderBox renderBox, BoxConstraints constraints) { - final bool isUserInteractionEnabled = - chart.zoomPanBehavior.enableDoubleTapZooming || - chart.zoomPanBehavior.enableMouseWheelZooming || - chart.zoomPanBehavior.enableSelectionZooming || - chart.zoomPanBehavior.enablePanning || - chart.zoomPanBehavior.enablePinching || - chart.trackballBehavior.enable || - chart.crosshairBehavior.enable || - chart.onChartTouchInteractionMove != null; - return Listener( - onPointerDown: (PointerDownEvent event) { - _performPointerDown(event); - ChartTouchInteractionArgs touchArgs; - if (chart.onChartTouchInteractionDown != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionDown(touchArgs); - } - }, - onPointerMove: (PointerMoveEvent event) { - _performPointerMove(event); - ChartTouchInteractionArgs touchArgs; - if (chart.onChartTouchInteractionMove != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionMove(touchArgs); - } - }, - onPointerUp: (PointerUpEvent event) { - _performPointerUp(event); - ChartTouchInteractionArgs touchArgs; - if (chart.onChartTouchInteractionUp != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionUp(touchArgs); - } - }, - onPointerSignal: (PointerSignalEvent event) { - if (event is PointerScrollEvent) { - _performPointerSignal(event); - } - }, - child: GestureDetector( - onTapDown: (TapDownDetails details) { - final Offset position = - renderBox.globalToLocal(details.globalPosition); - _touchPosition = position; - }, - onTapUp: (TapUpDetails details) { - final Offset position = - renderBox.globalToLocal(details.globalPosition); - final List visibleSeriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers; - if (chart.onPointTapped != null) { - _calculatePointSeriesIndex(chart, position); - } - if (chart.onAxisLabelTapped != null) { - _triggerAxisLabelEvent(position); - } - if (chart.onDataLabelTapped != null) { - _triggerDataLabelEvent(chart, visibleSeriesRenderer, position); - } - }, - onDoubleTap: () { - _performDoubleTap(); - }, - onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) { - _performLongPressMoveUpdate(details); - }, - onLongPress: () { - _performLongPress(); - }, - onLongPressEnd: (LongPressEndDetails details) { - _performLongPressEnd(); - }, - // onPanUpdate: (DragUpdateDetails details) { - // _performPanUpdate(details); - // }, - // onPanEnd: (DragEndDetails details) { - // _performPanEnd(details); - // }, - // onPanDown: (DragDownDetails details) { - // _performPanDown(details); - // }, - onVerticalDragUpdate: isUserInteractionEnabled - ? (DragUpdateDetails details) { - _performPanUpdate(details); - } - : null, - onVerticalDragEnd: isUserInteractionEnabled - ? (DragEndDetails details) { - _performPanEnd(details); - } - : null, - onVerticalDragDown: isUserInteractionEnabled - ? (DragDownDetails details) { - _performPanDown(details); - } - : null, - onHorizontalDragUpdate: isUserInteractionEnabled - ? (DragUpdateDetails details) { - _performPanUpdate(details); - } - : null, - onHorizontalDragEnd: isUserInteractionEnabled - ? (DragEndDetails details) { - _performPanEnd(details); - } - : null, - onHorizontalDragDown: isUserInteractionEnabled - ? (DragDownDetails details) { - _performPanDown(details); - } - : null, - child: Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - decoration: const BoxDecoration(color: Colors.transparent)))); + final Widget uiWidget = IgnorePointer( + ignoring: (chart.annotations != null), + child: Stack(children: userInteractionWidgets)); + _chartWidgets.add(uiWidget); } /// Find point index for selection @@ -3002,7 +3123,6 @@ class _ContainerArea extends StatelessWidget { _chartState._chartSeries.visibleSeriesRenderers[i]; final String _seriesType = seriesRenderer._seriesType; num pointIndex; - int count = 0; final double padding = (_seriesType == 'bubble') || (_seriesType == 'scatter') || (_seriesType == 'bar') || @@ -3024,14 +3144,18 @@ class _ContainerArea extends StatelessWidget { final double bottom = region.bottom + padding; final Rect paddedRegion = Rect.fromLTRB(left, top, right, bottom); if (paddedRegion.contains(position)) { - pointIndex = count; + pointIndex = regionRect[4].visiblePointIndex; } - count++; }); if (pointIndex != null) { PointTapArgs pointTapArgs; - pointTapArgs = PointTapArgs(i, pointIndex, seriesRenderer._dataPoints); + pointTapArgs = PointTapArgs( + i, + pointIndex, + seriesRenderer._dataPoints, + seriesRenderer + ._visibleDataPoints[pointIndex].overallDataPointIndex); chart.onPointTapped(pointTapArgs); } } @@ -3047,6 +3171,7 @@ class _ContainerArea extends StatelessWidget { for (int k = 0; k < labels.length; k++) { if (_chartState ._chartAxis._axisRenderersCollection[i]._axis.isVisible && + labels[k]._labelRegion != null && labels[k]._labelRegion.contains(position)) { AxisLabelTapArgs labelArgs; labelArgs = AxisLabelTapArgs( diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/base/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/chart/base/series_base.dart index 90a1cdfc5..34f0e4bf4 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/base/series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/base/series_base.dart @@ -40,6 +40,7 @@ class _ChartSeries { /// Find the data points for each series void _populateDataPoints(List seriesRendererList) { _chartState._totalAnimatingSeries = 0; + bool isSelectionRangeChangeByEvent = false; for (final CartesianSeriesRenderer seriesRenderer in seriesRendererList) { final CartesianSeries series = seriesRenderer._series; final ChartIndexedValueMapper _bubbleSize = series.sizeValueMapper; @@ -56,6 +57,9 @@ class _ChartSeries { yValues = []; sumOfYvalues = 0; seriesRenderer._dataPoints = >[]; + if (seriesRenderer is FastLineSeriesRenderer) { + seriesRenderer._overallDataPoints = >[]; + } seriesRenderer._xValues = []; if (!isStacked100 && seriesRenderer._seriesType.contains('100')) { isStacked100 = true; @@ -86,12 +90,15 @@ class _ChartSeries { if (series.dataSource != null) { dynamic xVal; dynamic yVal; + num low, high; num maxYValue = 0; 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; if (seriesRenderer is WaterfallSeriesRenderer) { yVal ??= 0; @@ -126,12 +133,37 @@ class _ChartSeries { _isYVisibleRange = false; } - if ((!(_chartState._zoomedState == true || + if (_chartState._chart.onSelectionChanged != null && + (xMin != null || + xMax != null || + yMin != null || + yMax != null) && + _chartState._oldSeriesRenderers != null && + _chartState._oldSeriesRenderers.isNotEmpty) { + final int seriesIndex = _chartState + ._chartSeries.visibleSeriesRenderers + .indexOf(seriesRenderer); + final CartesianSeriesRenderer _oldSeriesRenderer = + _chartState._oldSeriesRenderers.length - 1 >= seriesIndex + ? _chartState._oldSeriesRenderers[seriesIndex] + : null; + if (_oldSeriesRenderer != null) { + isSelectionRangeChangeByEvent = + _oldSeriesRenderer._minimumX != xMin || + _oldSeriesRenderer._maximumX != xMax || + _oldSeriesRenderer._minimumY != yMin || + _oldSeriesRenderer._maximumY != yMax; + } + } + + if ((!(isSelectionRangeChangeByEvent || + _chartState._zoomedState == true || _chartState._zoomProgress) && (xMin != null || xMax != null || yMin != null || - yMax != null)) + yMax != null) && + (yVal != null || (low != null && high != null))) ? ((xMin != null && xMax != null) ? (xPointValue >= xMin) && (xPointValue <= xMax) : xMin != null @@ -140,11 +172,11 @@ class _ChartSeries { ? xPointValue <= xMax : false) || ((yMin != null && yMax != null) - ? (yVal >= yMin) && (yVal <= yMax) + ? ((yVal ?? low) >= yMin) && ((yVal ?? high) <= yMax) : yMin != null - ? yVal >= yMin + ? (yVal ?? low) >= yMin : yMax != null - ? yVal <= yMax + ? (yVal ?? high) <= yMax : false) : true) { _isXVisibleRange = true; @@ -167,14 +199,14 @@ class _ChartSeries { if (seriesType.contains('range') || seriesType.contains('hilo') || seriesType.contains('candle') || - seriesType.contains('boxandwhisker') + seriesType == 'boxandwhisker' ? seriesType == 'hiloopenclose' || seriesType.contains('candle') ? (currentPoint.low == null || currentPoint.high == null || currentPoint.open == null || currentPoint.close == null) - : seriesType.contains('boxandwhisker') + : seriesType == 'boxandwhisker' ? (currentPoint.minimum == null || currentPoint.maximum == null || currentPoint.lowerQuartile == null || @@ -183,7 +215,7 @@ class _ChartSeries { currentPoint.high == null) : currentPoint.y == null) { if (seriesRenderer is XyDataSeriesRenderer && - !seriesType.contains('waterfall')) { + seriesType != 'waterfall') { seriesRenderer.calculateEmptyPointValue( pointIndex, currentPoint, seriesRenderer); } @@ -193,9 +225,9 @@ class _ChartSeries { if ((seriesType.contains('range') || seriesType.contains('hilo') || seriesType.contains('candle') || - seriesType.contains('boxandwhisker')) && + seriesType == 'boxandwhisker') && currentPoint.isVisible) { - if (seriesType.contains('boxandwhisker')) { + if (seriesType == 'boxandwhisker') { final num max = currentPoint.maximum; final num min = currentPoint.minimum; currentPoint.maximum = math.max(max, min); @@ -265,6 +297,9 @@ class _ChartSeries { } } } + if (seriesRenderer is FastLineSeriesRenderer) { + seriesRenderer._overallDataPoints.addAll(seriesRenderer._dataPoints); + } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/line_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/line_segment.dart index 64523a508..5d0a5aefa 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/line_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/line_segment.dart @@ -18,7 +18,7 @@ class LineSegment extends ChartSegment { Color _pointColorMapper; - bool _needAnimate; + bool _needAnimate, _newlyAddedSegment = false; Rect _axisClipRect; @@ -105,6 +105,8 @@ class LineSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + num prevX, prevY; + LineSegment prevSegment; final Rect rect = _calculatePlotOffset( _seriesRenderer._chartState._chartAxis._axisClipRect, Offset(_seriesRenderer._xAxisRenderer._axis.plotOffset, @@ -124,11 +126,25 @@ class LineSegment extends ChartSegment { currentSegmentIndex) ? _currentSegment._oldSeriesRenderer._segments[currentSegmentIndex] : null; + if (currentSegmentIndex > 0) { + prevSegment = + (_currentSegment._oldSeriesRenderer._segments.length - 1 >= + currentSegmentIndex - 1) + ? _currentSegment + ._oldSeriesRenderer._segments[currentSegmentIndex - 1] + : null; + } _oldX1 = _oldSegment?._x1; _oldY1 = _oldSegment?._y1; _oldX2 = _oldSegment?._x2; _oldY2 = _oldSegment?._y2; - + if (_oldSegment == null && _chartState._widgetNeedUpdate) { + _newlyAddedSegment = true; + prevX = prevSegment?._x2; + prevY = prevSegment?._y2; + } else { + _newlyAddedSegment = false; + } if (_oldSegment != null && (_oldX1.isNaN || _oldX2.isNaN) && _seriesRenderer._chartState._oldAxisRenderers != null && @@ -170,20 +186,34 @@ class LineSegment extends ChartSegment { _oldY2 = _second.y; } } - _animateLineTypeSeries( - canvas, - _seriesRenderer, - strokePaint, - animationFactor, - _currentSegment._x1, - _currentSegment._y1, - _currentSegment._x2, - _currentSegment._y2, - _oldX1, - _oldY1, - _oldX2, - _oldY2, - ); + if (_newlyAddedSegment) { + _animateToPoint( + canvas, + _seriesRenderer, + strokePaint, + animationFactor, + _currentSegment._x1, + _currentSegment._y1, + _currentSegment._x2, + _currentSegment._y2, + prevX, + prevY); + } else { + _animateLineTypeSeries( + canvas, + _seriesRenderer, + strokePaint, + animationFactor, + _currentSegment._x1, + _currentSegment._y1, + _currentSegment._x2, + _currentSegment._y2, + _oldX1, + _oldY1, + _oldX2, + _oldY2, + ); + } } else { if (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) { _path.moveTo(_x1, _y1); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stepline_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stepline_segment.dart index 7d717c64a..7c24c15fa 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stepline_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stepline_segment.dart @@ -68,9 +68,36 @@ class StepLineSegment extends ChartSegment { /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - _currentLocation = _currentPoint.currentPoint; - _nextLocation = _currentPoint._nextPoint; - _midLocation = _currentPoint._midPoint; + final ChartAxisRenderer _xAxisRenderer = _seriesRenderer._xAxisRenderer; + final ChartAxisRenderer _yAxisRenderer = _seriesRenderer._yAxisRenderer; + final Rect _axisClipRect = _calculatePlotOffset( + _chartState._chartAxis._axisClipRect, + Offset(_seriesRenderer._xAxisRenderer._axis.plotOffset, + _seriesRenderer._yAxisRenderer._axis.plotOffset)); + _currentLocation = _calculatePoint( + _currentPoint.xValue, + _currentPoint.yValue, + _xAxisRenderer, + _yAxisRenderer, + _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._series, + _axisClipRect); + _nextLocation = _calculatePoint( + _nextPoint.xValue, + _nextPoint.yValue, + _xAxisRenderer, + _yAxisRenderer, + _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._series, + _axisClipRect); + _midLocation = _calculatePoint( + _midX, + _midY, + _xAxisRenderer, + _yAxisRenderer, + _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._series, + _axisClipRect); _x1 = _currentLocation.x; _y1 = _currentLocation.y; _x2 = _nextLocation.x; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/fastline_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/fastline_series.dart index a8bdab110..0b2b602e1 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/fastline_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/fastline_series.dart @@ -94,6 +94,10 @@ class FastLineSeriesRenderer extends XyDataSeriesRenderer { /// Calling the default constructor of FastLineSeriesRenderer class. FastLineSeriesRenderer(); + //ignore: prefer_final_fields + List> _overallDataPoints = + >[]; + ///Adds the segment to the segments list ChartSegment _createSegments( int seriesIndex, SfCartesianChart chart, num animateFactor, diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_column_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_column_series.dart index 9fcab08f3..ca708e946 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_column_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_column_series.dart @@ -268,6 +268,7 @@ class RangeColumnSeriesRenderer extends XyDataSeriesRenderer { /// To add range column segments in segments list ChartSegment _createSegments(CartesianChartPoint currentPoint, int pointIndex, int seriesIndex, num animateFactor) { + _isRectSeries = true; final RangeColumnSegment segment = createSegment(); final List oldSeriesRenderers = _chartState._oldSeriesRenderers; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/scatter_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/scatter_series.dart index 6cf9afdaf..a973c6314 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/scatter_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/scatter_series.dart @@ -188,7 +188,7 @@ class ScatterSeriesRenderer extends XyDataSeriesRenderer { final Size size = Size(_series.markerSettings.width, _series.markerSettings.height); final Path markerPath = _getMarkerShapesPath(_series.markerSettings.shape, - Offset(pointX, pointY), size, seriesRenderer); + Offset(pointX, pointY), size, seriesRenderer, index); canvas.drawPath(markerPath, fillPaint); canvas.drawPath(markerPath, strokePaint); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series.dart index c84c65bbd..041f8c762 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series.dart @@ -1319,6 +1319,149 @@ class ChartSeriesController { } } + /// 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. + /// + /// _Note_: This method is only applicable for cartesian chart, not for the circular, pyramid, + /// and funnel charts. + /// + ///```dart + /// Widget build(BuildContext context) { + /// ChartSeriesController seriesController; + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onRendererCreated: (ChartSeriesController controller) { + /// seriesController = controller; + /// }, + /// ) + /// ], + /// onChartTouchInteractionUp: (ChartTouchInteractionArgs args) { + /// final Offset value = Offset(args.position.dx, args.position.dy); + /// CartesianChartPoint chartpoint = + /// seriesController.pixelToPoint(value); + /// print('X point: ${chartpoint.x}'); + /// print('Y point: ${chartpoint.y}'); + /// } + /// ) + /// ); + /// } + ///``` + + CartesianChartPoint pixelToPoint(Offset position) { + ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; + ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + + final ChartAxis xAxis = xAxisRenderer._axis; + final ChartAxis yAxis = yAxisRenderer._axis; + + final CartesianSeries series = seriesRenderer._series; + + final Rect rect = seriesRenderer._chartState._chartAxis._axisClipRect; + + if (series.xAxisName != null || series.yAxisName != null) { + for (final ChartAxisRenderer axisRenderer + in seriesRenderer._chartState._chartAxis._axisRenderersCollection) { + if (xAxis.name == series.xAxisName) { + xAxisRenderer = axisRenderer; + } else if (yAxis.name == series.yAxisName) { + yAxisRenderer = axisRenderer; + } + } + } else { + xAxisRenderer = xAxisRenderer; + yAxisRenderer = yAxisRenderer; + } + + num xValue = _pointToXValue( + seriesRenderer._chartState._requireInvertedAxis, + xAxisRenderer, + xAxisRenderer._bounds, + position.dx - (rect.left + xAxis.plotOffset), + position.dy - (rect.top + yAxis.plotOffset)); + num yValue = _pointToYValue( + seriesRenderer._chartState._requireInvertedAxis, + yAxisRenderer, + yAxisRenderer._bounds, + position.dx - (rect.left + xAxis.plotOffset), + position.dy - (rect.top + yAxis.plotOffset)); + + if (xAxisRenderer is LogarithmicAxisRenderer) { + final LogarithmicAxis axis = xAxis; + xValue = math.pow(xValue, _calculateLogBaseValue(xValue, axis.logBase)); + } else { + xValue = xValue; + } + if (yAxisRenderer is LogarithmicAxisRenderer) { + final LogarithmicAxis axis = yAxis; + yValue = math.pow(yValue, _calculateLogBaseValue(yValue, axis.logBase)); + } else { + yValue = yValue; + } + return CartesianChartPoint(xValue, yValue); + } + + /// 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. + /// + /// _Note_: This method is only applicable for cartesian chart, not for the circular, pyramid, + /// and funnel charts. + /// + ///```dart + /// Widget build(BuildContext context) { + /// ChartSeriesController seriesController; + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// onRendererCreated: (ChartSeriesController controller) { + /// seriesController = controller; + /// }, + /// ) + /// ], + /// onPointTapped: (PointTapArgs args) { + /// CartesianChartPoint chartPoint = + /// CartesianChartPoint(data[args.pointIndex].x, + /// data[args.pointIndex].y); + /// Offset pointLocation = seriesController.pointToPixel(chartPoint); + /// print('X location: ${pointLocation.x}'); + /// print('Y location: ${pointLocation.y}'); + /// }, + /// ) + /// ); + /// } + ///``` + Offset pointToPixel(CartesianChartPoint point) { + final num x = point.x; + final num y = point.y; + + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + + final bool isInverted = seriesRenderer._chartState._requireInvertedAxis; + + final CartesianSeries series = seriesRenderer._series; + final _ChartLocation location = _calculatePoint( + x, + y, + xAxisRenderer, + yAxisRenderer, + isInverted, + series, + seriesRenderer._chartState._containerRect); + + return Offset(location.x, location.y); + } + ///If you wish to perform initial animation again in the existing series, this method can be called. /// On calling this method, this particular series will be animated again based on the [animationDuration] /// property's value in the series. If the value is 0, then the animation will not be performed. @@ -1360,6 +1503,9 @@ class ChartSeriesController { chartState._tooltipBehaviorRenderer._tooltipTemplate; final _TrackballPainter trackballPainter = chartState._trackballBehaviorRenderer._trackballPainter; + final TrackballBehaviorRenderer trackballBehaviorRenderer = + chartState._trackballBehaviorRenderer; + //This hides the tooltip if rendered for this current series renderer if (tooltip != null && tooltip.enable && @@ -1370,11 +1516,23 @@ class ChartSeriesController { tooltip.hide(); } //This hides the trackball if rendered for this current series renderer - if (trackball != null && trackball.enable) { - for (final point in trackballPainter.chartPointInfo) { + if (trackball != null && + trackball.enable && + trackballBehaviorRenderer != null) { + for (final point in trackballBehaviorRenderer._chartPointInfo) { if (point.seriesRenderer == seriesRenderer) { - trackball.hide(); - break; + if (trackballPainter != null) { + chartState._trackballRepaintNotifier.value++; + trackballPainter.canResetPath = true; + break; + } else { + final GlobalKey key = + trackballBehaviorRenderer._trackballTemplate.key; + final _TrackballTemplateState trackballTemplateState = + key.currentState; + trackballTemplateState.hideTrackballTemplate(); + break; + } } } } @@ -1488,7 +1646,14 @@ class ChartSeriesController { axisRenderer._calculateRangeAndInterval(chartState); } if (needXRecalculation || needYRecalculation) { - chartState._renderAxis.state.axisRepaintNotifier.value++; + chartState._renderOutsideAxis.state.axisRepaintNotifier.value++; + chartState._renderInsideAxis.state.axisRepaintNotifier.value++; + for (final seriesRenderer + in chartState._chartSeries.visibleSeriesRenderers) { + _repaintSeries(chartState, seriesRenderer); + } + } else { + _repaintSeries(chartState, seriesRenderer); } //This makes the update data source method work with dynamic animation(scheduled for release) // seriesRenderer._needsAnimation = seriesRenderer._needAnimateSeriesElements = @@ -1498,7 +1663,16 @@ class ChartSeriesController { // seriesRenderer._chartState._animationCompleteCount = 0; // seriesRenderer._chartState._forwardAnimation( // seriesRenderer, seriesRenderer._series.animationDuration); + } + + //This method repaints the series and its elements for the given series renderer + void _repaintSeries(SfCartesianChartState chartState, + CartesianSeriesRenderer seriesRenderer) { + seriesRenderer._calculateRegion = true; seriesRenderer._repaintNotifier.value++; + if (seriesRenderer._series.dataLabelSettings.isVisible) { + chartState._renderDataLabel.state.dataLabelRepaintNotifier.value++; + } } } @@ -1508,7 +1682,8 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer { String _seriesType; /// Whether to check the series is rect series or not - bool _isRectSeries; + // ignore: prefer_final_fields + bool _isRectSeries = false; final List<_ListControlPoints> _drawControlPoints = <_ListControlPoints>[]; @@ -1535,9 +1710,15 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer { 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 ChartAxisRenderer _xAxisRenderer; @@ -1600,6 +1781,8 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer { //ignore: prefer_final_fields bool _reAnimate = false; + bool _calculateRegion = false; + //ignore: prefer_final_fields Animation _seriesAnimation; @@ -1630,6 +1813,15 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer { // ignore: prefer_final_fields bool _isMarkerRenderEvent = false; + // bool for animation status + bool _animationCompleted; + + // ignore: prefer_final_fields + bool _hasDataLabelTemplate = false; + + // ignore: prefer_final_fields + _VisibleRange sideBySideInfo; + /// To create segment for series ChartSegment createSegment(); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stepline_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stepline_series.dart index cbc55fb71..e65203750 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stepline_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stepline_series.dart @@ -103,10 +103,6 @@ class StepLineSeriesRenderer extends XyDataSeriesRenderer { _chartState._oldSeriesRenderers; _isRectSeries = false; segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint.x, currentPoint.markerPoint.y)); - segment.points - .add(Offset(currentPoint._nextPoint.x, currentPoint._nextPoint.y)); segment._seriesIndex = seriesIndex; segment._seriesRenderer = this; segment._series = _series; @@ -127,6 +123,8 @@ class StepLineSeriesRenderer extends XyDataSeriesRenderer { segment._oldSeriesRenderer = _oldSeriesRenderers[segment._seriesIndex]; } segment.calculateSegmentPoints(); + segment.points.add(Offset(segment._x1, segment._y1)); + segment.points.add(Offset(segment._x2, segment._y2)); customizeSegment(segment); segment.strokePaint = segment.getStrokePaint(); segment.fillPaint = segment.getFillPaint(); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/xy_data_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/xy_data_series.dart index f46c7d7c4..7c0e3bc6c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/xy_data_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/xy_data_series.dart @@ -284,6 +284,12 @@ class CartesianChartPoint { // ignore: prefer_final_fields bool isTooltipRenderEvent = false; + //specifies the style of data label in the onDataLabelEvent + TextStyle _dataLabelStyle; + + //specifies the color of the data label in onDataLabelEvent + Color _dataLabelColor; + /// Stores the chart location. _ChartLocation openPoint, closePoint, @@ -294,7 +300,9 @@ class CartesianChartPoint { centerLowPoint, centerHighPoint, currentPoint, + // ignore:unused_field _nextPoint, + // ignore:unused_field _midPoint, startControl, endControl, @@ -328,9 +336,6 @@ class CartesianChartPoint { /// control points for spline range area series. List<_ControlPoints> controlPointslow; - /// Store the visible range. - _VisibleRange sideBySideInfo; - /// Store the List of region. List regions; @@ -423,6 +428,9 @@ class CartesianChartPoint { /// Store the region data of the data point. List regionData; + + /// Store the visible point index. + int visiblePointIndex; } class _ChartLocation { @@ -626,10 +634,12 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { void _animationStatusListener(AnimationStatus status) { if (_chartState != null && status == AnimationStatus.completed) { _reAnimate = false; + _animationCompleted = true; _chartState?._animationCompleteCount++; _setAnimationStatus(_chartState); } else if (_chartState != null && status == AnimationStatus.forward) { _chartState?._animateCompleted = false; + _animationCompleted = false; } } @@ -677,6 +687,15 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { CartesianChartPoint _nextPoint, num midX, num midY]) { + if (_withInRange(seriesRenderer._dataPoints[pointIndex].xValue, + seriesRenderer._xAxisRenderer._visibleRange)) { + seriesRenderer._visibleDataPoints + .add(seriesRenderer._dataPoints[pointIndex]); + if (seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._dataPoints[pointIndex].visiblePointIndex = + seriesRenderer._visibleDataPoints.length - 1; + } + } _chart = _chartState._chart; final ChartAxis xAxis = _xAxisRenderer._axis; final ChartAxis yAxis = _yAxisRenderer._axis; @@ -690,6 +709,7 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { _seriesType == 'histogram' || _seriesType == 'waterfall'; CartesianChartPoint point; + final num markerHeight = _series.markerSettings.height, markerWidth = _series.markerSettings.width; final bool isPointSeries = @@ -703,8 +723,22 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { _visible) { point = _dataPoints[pointIndex]; if (point.region == null || + seriesRenderer._calculateRegion || _seriesType.contains('stackedcolumn') || _seriesType.contains('stackedbar')) { + if (seriesRenderer._calculateRegion && + _dataPoints.length == pointIndex - 1) { + seriesRenderer._calculateRegion = false; + } + + /// side by side range calculated + seriesRenderer.sideBySideInfo = (_isRectSeries || + (_seriesType.contains('candle') || + _seriesType.contains('hilo') || + _seriesType.contains('histogram') || + _seriesType.contains('box'))) + ? _calculateSideBySideInfo(seriesRenderer, _chartState) + : seriesRenderer.sideBySideInfo; if (_isRectSeries) { _calculateRectSeriesRegion(point, pointIndex, this, _chartState); } else if (isPointSeries) { @@ -726,7 +760,7 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { } } if (_chart.tooltipBehavior != null && - _chart.tooltipBehavior.enable && + (_chart.tooltipBehavior.enable || _chart.onPointTapped != null) && _seriesType != 'boxandwhisker') { _calculateTooltipRegion(point, seriesIndex, this, _chartState); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart index c118a1a16..3f7c1c275 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart @@ -311,6 +311,9 @@ class _ChartPointInfo { /// Low Y position of financial series double lowYPosition; + /// High X position of financial series + double highXPosition; + /// High Y position of financial series double highYPosition; @@ -344,6 +347,9 @@ class _ChartPointInfo { /// Upper x position of box plot series double upperYPosition; + // Maximum x position forr vox plot series + double maxXPosition; + /// series index value num seriesIndex; } @@ -409,6 +415,22 @@ class TrackballMarkerSettings extends MarkerSettings { final TrackballVisibilityMode markerVisibility; } +///Options to show the details of the trackball template. +class TrackballDetails { + ///Constructor of TrackballDetails class. + TrackballDetails( + [this.point, + this.series, + this.pointIndex, + this.seriesIndex, + this.groupingModeInfo]); + final CartesianChartPoint point; + final CartesianSeries series; + final int pointIndex; + final int seriesIndex; + final TrackballGroupingModeInfo groupingModeInfo; +} + /// To get cartesian type data label saturation color Color _getDataLabelSaturationColor( CartesianChartPoint currentPoint, @@ -450,10 +472,13 @@ Color _getDataLabelSaturationColor( break; case 'Column': color = (!currentPoint.dataLabelSaturationRegionInside && - ((labelPosition == ChartDataLabelAlignment.outer && - alignment != ChartAlignment.near) || + (labelPosition == ChartDataLabelAlignment.outer || (labelPosition == ChartDataLabelAlignment.top && alignment == ChartAlignment.far) || + (seriesRenderer._seriesType == 'rangecolumn' + ? (labelPosition == ChartDataLabelAlignment.top && + alignment == ChartAlignment.near) + : false) || (labelPosition == ChartDataLabelAlignment.auto && (!seriesRenderer._seriesType.contains('100') && seriesRenderer._seriesType != 'stackedbar' && @@ -694,8 +719,11 @@ void _animateTransposedRectSeries( } canvas.drawRRect( - RRect.fromRectAndRadius( - rect ?? segmentRect?.middleRect, segmentRect.blRadius), + RRect.fromRectAndCorners(rect ?? segmentRect?.middleRect, + bottomLeft: segmentRect.blRadius, + bottomRight: segmentRect.brRadius, + topLeft: segmentRect.tlRadius, + topRight: segmentRect.trRadius), fillPaint); } @@ -836,8 +864,11 @@ void _animateNormalRectSeries( oldSegmentRect, oldSeriesVisible, isSingleSeries, animationFactor); } canvas.drawRRect( - RRect.fromRectAndRadius( - rect ?? segmentRect?.middleRect, segmentRect.blRadius), + RRect.fromRectAndCorners(rect ?? segmentRect?.middleRect, + bottomLeft: segmentRect.blRadius, + bottomRight: segmentRect.brRadius, + topLeft: segmentRect.tlRadius, + topRight: segmentRect.trRadius), fillPaint); } @@ -997,9 +1028,12 @@ void _drawAnimatedStackedRect( ? top = (segmentRect.top + segmentRect.height) - height : top = prevRegion.top - height; - segmentRect = RRect.fromRectAndRadius( + segmentRect = RRect.fromRectAndCorners( Rect.fromLTWH(segmentRect.left, top, segmentRect.width, height), - segmentRect.blRadius); + 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); @@ -1023,9 +1057,12 @@ void _drawAnimatedStackedRect( ? right = (segmentRect.right - segmentRect.width) + width : right = prevRegion.right + width; - segmentRect = RRect.fromRectAndRadius( + segmentRect = RRect.fromRectAndCorners( Rect.fromLTWH(right - width, top1, width, height1), - segmentRect.blRadius); + bottomLeft: segmentRect.blRadius, + bottomRight: segmentRect.brRadius, + topLeft: segmentRect.tlRadius, + topRight: segmentRect.trRadius); currentPoint.region = Rect.fromLTWH(segmentRect.left, right, segmentRect.width, width); canvas.drawRRect(segmentRect, fillPaint); @@ -1096,6 +1133,28 @@ void _animateLineTypeSeries( _drawDashedLine(canvas, seriesRenderer._series.dashArray, strokePaint, path); } +void _animateToPoint( + Canvas canvas, + CartesianSeriesRenderer seriesRenderer, + Paint strokePaint, + double animationFactor, + num x1, + num y1, + num x2, + num y2, + num prevX, + num prevY) { + final Path path = Path(); + prevX ??= x1; + prevY ??= y1; + final num newX1 = prevX + (x1 - prevX) * animationFactor; + final num newY1 = prevY + (y1 - prevY) * animationFactor; + path.moveTo(newX1, newY1); + path.lineTo(newX1 + (x2 - newX1) * animationFactor, + newY1 + (y2 - newY1) * animationFactor); + _drawDashedLine(canvas, seriesRenderer._series.dashArray, strokePaint, path); +} + /// To get value of animation based on animation factor num _getAnimateValue( double animationFactor, double value, num oldvalue, num newValue, @@ -1241,8 +1300,11 @@ void _animateRangeColumn( } } canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTWH(left, top, width, height), segmentRect.blRadius), + RRect.fromRectAndCorners(Rect.fromLTWH(left, top, width, height), + bottomLeft: segmentRect.blRadius, + bottomRight: segmentRect.brRadius, + topLeft: segmentRect.tlRadius, + topRight: segmentRect.trRadius), fillPaint); } @@ -1880,7 +1942,7 @@ void _calculateImage( if (chart.plotAreaBackgroundImage != null) { chartState._backgroundImage = await _getImageInfo(chart.plotAreaBackgroundImage); - chartState._renderAxis.state.axisRepaintNotifier.value++; + chartState._renderOutsideAxis.state.axisRepaintNotifier.value++; } if (chart.legend.image != null) { chartState._legendIconImage = await _getImageInfo(chart.legend.image); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label.dart index afc57004c..171ad38a5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label.dart @@ -441,6 +441,7 @@ class DataLabelSettingsRenderer { /// Creates an argument constructor for DataLabelSettings renderer class DataLabelSettingsRenderer(this._dataLabelSettings) { _angle = _dataLabelSettings.angle; + _offset = _dataLabelSettings.offset; } final DataLabelSettings _dataLabelSettings; @@ -453,6 +454,8 @@ class DataLabelSettingsRenderer { int _angle; + Offset _offset; + /// To render charts with data labels void _renderDataLabel( SfCartesianChartState chartState, diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label_renderer.dart index d72427104..2067ef69e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label_renderer.dart @@ -77,33 +77,34 @@ void _calculateDataLabelPosition( } DataLabelRenderArgs dataLabelArgs; TextStyle dataLabelStyle = dataLabelSettingsRenderer._textStyle; - DataLabelSettings labelSettings; - labelSettings = DataLabelSettings(color: dataLabel.color); - final DataLabelSettingsRenderer labelSettingsRenderer = - DataLabelSettingsRenderer(labelSettings); //ignore: prefer_conditional_assignment if (dataLabelSettingsRenderer._originalStyle == null) { dataLabelSettingsRenderer._originalStyle = dataLabel.textStyle; } dataLabelStyle = dataLabelSettingsRenderer._originalStyle; - labelSettingsRenderer._color = dataLabel.color; if (chart.onDataLabelRender != null && - !seriesRenderer._dataPoints[index].labelRenderEvent) { - seriesRenderer._dataPoints[index].labelRenderEvent = true; - dataLabelArgs = - DataLabelRenderArgs(seriesRenderer, seriesRenderer._dataPoints, index); + !seriesRenderer._visibleDataPoints[index].labelRenderEvent) { + seriesRenderer._visibleDataPoints[index].labelRenderEvent = true; + dataLabelArgs = DataLabelRenderArgs( + seriesRenderer._series, + seriesRenderer._dataPoints, + index, + seriesRenderer._visibleDataPoints[index].overallDataPointIndex); dataLabelArgs.text = label; dataLabelArgs.textStyle = dataLabelStyle; - dataLabelArgs.color = labelSettingsRenderer._color; + dataLabelArgs.color = dataLabelSettingsRenderer._color; chart.onDataLabelRender(dataLabelArgs); label = point.label = dataLabelArgs.text; - dataLabelStyle = dataLabelArgs.textStyle; index = dataLabelArgs.pointIndex; - labelSettingsRenderer._color = dataLabelArgs.color; + point._dataLabelStyle = dataLabelArgs.textStyle; + point._dataLabelColor = dataLabelArgs.color; } dataLabelSettingsRenderer._textStyle = dataLabelStyle; - dataLabelSettingsRenderer._color = labelSettingsRenderer._color; - labelSettingsRenderer._color = null; + if (chart.onDataLabelRender != null) { + dataLabelSettingsRenderer._color = point._dataLabelColor; + dataLabelSettingsRenderer._textStyle = point._dataLabelStyle; + dataLabelStyle = dataLabelSettingsRenderer._textStyle; + } if (point != null && point.isVisible && point.isGap != true && @@ -1151,9 +1152,9 @@ void _drawDataLabel( DataLabelSettingsRenderer dataLabelSettingsRenderer) { double x = 0; double y = 0; - if (dataLabel.offset != null) { - x = dataLabel.offset.dx; - y = dataLabel.offset.dy; + if (dataLabelSettingsRenderer._offset != null) { + x = dataLabelSettingsRenderer._offset.dx; + y = dataLabelSettingsRenderer._offset.dy; } final double opacity = seriesRenderer._needAnimateSeriesElements && dataLabelAnimation != null @@ -1221,21 +1222,25 @@ void _drawDataLabel( void _triggerDataLabelEvent(SfCartesianChart chart, List visibleSeriesRenderer, Offset position) { - for (int i = 0; i < visibleSeriesRenderer.length; i++) { - final CartesianSeriesRenderer seriesRenderer = visibleSeriesRenderer[i]; + for (int seriesIndex = 0; + seriesIndex < visibleSeriesRenderer.length; + seriesIndex++) { + final CartesianSeriesRenderer seriesRenderer = + visibleSeriesRenderer[seriesIndex]; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRenderer._visibleDataPoints; for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { if (seriesRenderer._series.dataLabelSettings.isVisible && + 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, seriesRenderer._series.dataLabelSettings, - pointIndex, point, position); + pointIndex, point, position, seriesIndex); + break; } } - break; } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart index 0850a4b1e..147a278e1 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart @@ -211,6 +211,8 @@ class MarkerSettingsRenderer { Canvas canvas, int markerIndex, [int outlierIndex]) { + final bool isDataPointVisible = _isLabelWithinRange( + seriesRenderer, seriesRenderer._dataPoints[markerIndex]); Paint strokePaint, fillPaint; final XyDataSeries series = seriesRenderer._series; final Size size = @@ -235,35 +237,41 @@ class MarkerSettingsRenderer { _color = series.markerSettings.color; _borderWidth = series.markerSettings.borderWidth; if (!isBoxSeries) { - seriesRenderer._markerShapes.add(_getMarkerShapesPath( - markerType, - Offset(point.markerPoint.x, point.markerPoint.y), - size, - seriesRenderer, - markerIndex, - null, - animationController)); + seriesRenderer._markerShapes.add(isDataPointVisible + ? _getMarkerShapesPath( + markerType, + Offset(point.markerPoint.x, point.markerPoint.y), + size, + seriesRenderer, + markerIndex, + null, + animationController) + : null); } else { - seriesRenderer._markerShapes.add(_getMarkerShapesPath( - markerType, - Offset(point.outliersPoint[outlierIndex].x, - point.outliersPoint[outlierIndex].y), - size, - seriesRenderer, - markerIndex, - null, - animationController)); + seriesRenderer._markerShapes.add(isDataPointVisible + ? _getMarkerShapesPath( + markerType, + Offset(point.outliersPoint[outlierIndex].x, + point.outliersPoint[outlierIndex].y), + size, + seriesRenderer, + markerIndex, + null, + animationController) + : null); } if (seriesRenderer._seriesType.contains('range') || seriesRenderer._seriesType == 'hilo') { - seriesRenderer._markerShapes2.add(_getMarkerShapesPath( - markerType, - Offset(point.markerPoint2.x, point.markerPoint2.y), - size, - seriesRenderer, - markerIndex, - null, - animationController)); + seriesRenderer._markerShapes2.add(isDataPointVisible + ? _getMarkerShapesPath( + markerType, + Offset(point.markerPoint2.x, point.markerPoint2.y), + size, + seriesRenderer, + markerIndex, + null, + animationController) + : null); } strokePaint = Paint() ..color = point.isEmpty == true @@ -328,7 +336,10 @@ class MarkerSettingsRenderer { (point.markerPoint != null || point.outliersPoint[outlierIndex] != null) && point.isGap != true && - (!isScatter || series.markerSettings.shape == DataMarkerType.image)) { + (!isScatter || series.markerSettings.shape == DataMarkerType.image) && + seriesRenderer + ._markerShapes[isBoxSeries ? outlierIndex : markerIndex] != + null) { seriesRenderer.drawDataMarker( isBoxSeries ? outlierIndex : markerIndex, canvas, diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart index faedd5da7..6146418e4 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart @@ -105,29 +105,34 @@ class _DataLabelPainter extends CustomPainter { cartesianChartState._chartSeries.visibleSeriesRenderers[i]; DataLabelSettingsRenderer dataLabelSettingsRenderer; if (seriesRenderer._series.dataLabelSettings.isVisible && - (seriesRenderer._animationController.status == - AnimationStatus.completed || + (seriesRenderer._animationCompleted || seriesRenderer._series.animationDuration == 0 || !cartesianChartState._initialRender) && (!seriesRenderer._needAnimateSeriesElements || (cartesianChartState._seriesDurationFactor < seriesRenderer._animationController.value || - seriesRenderer._series.animationDuration == 0)) && + seriesRenderer._series.animationDuration == 0) || + (seriesRenderer._animationController.status == + AnimationStatus.dismissed)) && seriesRenderer._series.dataLabelSettings.builder == null) { - for (int j = 0; j < seriesRenderer._dataPoints.length; j++) { - if (seriesRenderer._visible && - seriesRenderer._series.dataLabelSettings != null) { - seriesRenderer._dataLabelSettingsRenderer = - dataLabelSettingsRenderer = DataLabelSettingsRenderer( - seriesRenderer._series.dataLabelSettings); - dataLabelSettingsRenderer?._renderDataLabel( - cartesianChartState, - seriesRenderer, - seriesRenderer._dataPoints[j], - animation, - canvas, - j, - dataLabelSettingsRenderer); + seriesRenderer._dataLabelSettingsRenderer = + DataLabelSettingsRenderer(seriesRenderer._series.dataLabelSettings); + if (seriesRenderer._visibleDataPoints != null && + seriesRenderer._visibleDataPoints.isNotEmpty) { + for (int j = 0; j < seriesRenderer._visibleDataPoints.length; j++) { + if (seriesRenderer._visible && + seriesRenderer._series.dataLabelSettings != null) { + dataLabelSettingsRenderer = + seriesRenderer._dataLabelSettingsRenderer; + dataLabelSettingsRenderer?._renderDataLabel( + cartesianChartState, + seriesRenderer, + seriesRenderer._visibleDataPoints[j], + animation, + canvas, + j, + dataLabelSettingsRenderer); + } } } if (animation.value >= 1) { @@ -150,10 +155,10 @@ void _calculateRectSeriesRegion( final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; final num crossesAt = _getCrossesAtValue(seriesRenderer, _chartState); + final num sideBySideMinimumVal = seriesRenderer.sideBySideInfo.minimum; + + final num sideBySideMaximumVal = seriesRenderer.sideBySideInfo.maximum; - /// side by side range calculated - final _VisibleRange sideBySideInfo = - _calculateSideBySideInfo(seriesRenderer, _chartState); final num origin = crossesAt ?? math.max(yAxisRenderer._visibleRange.minimum, 0); @@ -162,15 +167,15 @@ void _calculateRectSeriesRegion( seriesRenderer._seriesType.contains('stackedbar')) && seriesRenderer is _StackedSeriesRenderer ? _calculateRectangle( - point.xValue + sideBySideInfo.minimum, + point.xValue + sideBySideMinimumVal, seriesRenderer._stackingValues[0].endValues[pointIndex], - point.xValue + sideBySideInfo.maximum, + point.xValue + sideBySideMaximumVal, crossesAt ?? seriesRenderer._stackingValues[0].startValues[pointIndex], seriesRenderer, _chartState) : _calculateRectangle( - point.xValue + sideBySideInfo.minimum, + point.xValue + sideBySideMinimumVal, seriesRenderer._seriesType == 'rangecolumn' ? point.high : seriesRenderer._seriesType == 'boxandwhisker' @@ -178,7 +183,7 @@ void _calculateRectSeriesRegion( : seriesRenderer._seriesType == 'waterfall' ? point.endValue : point.yValue, - point.xValue + sideBySideInfo.maximum, + point.xValue + sideBySideMaximumVal, seriesRenderer._seriesType == 'rangecolumn' ? point.low : seriesRenderer._seriesType == 'boxandwhisker' @@ -198,9 +203,9 @@ void _calculateRectSeriesRegion( seriesRenderer._seriesType != 'waterfall' && series.isTrackVisible) { final Rect shadowPointRect = _calculateShadowRectangle( - point.xValue + sideBySideInfo.minimum, + point.xValue + sideBySideMinimumVal, seriesRenderer._seriesType == 'rangecolumn' ? point.high : point.yValue, - point.xValue + sideBySideInfo.maximum, + point.xValue + sideBySideMaximumVal, seriesRenderer._seriesType == 'rangecolumn' ? point.high : origin, seriesRenderer, _chartState, @@ -238,7 +243,7 @@ void _calculateRectSeriesRegion( if (seriesRenderer._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 + sideBySideInfo.minimum, + point.xValue + sideBySideMinimumVal, point.originValue, xAxisRenderer, yAxisRenderer, @@ -249,7 +254,7 @@ void _calculateRectSeriesRegion( Offset(xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset))); point.originValueRightPoint = _calculatePoint( - point.xValue + sideBySideInfo.maximum, + point.xValue + sideBySideMaximumVal, point.originValue, xAxisRenderer, yAxisRenderer, @@ -260,7 +265,7 @@ void _calculateRectSeriesRegion( Offset(xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset))); point.endValueLeftPoint = _calculatePoint( - point.xValue + sideBySideInfo.minimum, + point.xValue + sideBySideMinimumVal, point.endValue, xAxisRenderer, yAxisRenderer, @@ -271,7 +276,7 @@ void _calculateRectSeriesRegion( Offset(xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset))); point.endValueRightPoint = _calculatePoint( - point.xValue + sideBySideInfo.maximum, + point.xValue + sideBySideMaximumVal, point.endValue, xAxisRenderer, yAxisRenderer, @@ -360,6 +365,9 @@ void _calculatePathSeriesRegion( num midY]) { final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final num sideBySideMinimumVal = seriesRenderer?.sideBySideInfo?.minimum; + + final num sideBySideMaximumVal = seriesRenderer?.sideBySideInfo?.maximum; if (seriesRenderer._seriesType != 'rangearea' && seriesRenderer._seriesType != 'splinerangearea' && (!seriesRenderer._seriesType.contains('hilo')) && @@ -374,7 +382,7 @@ void _calculatePathSeriesRegion( point.controlPoint[0].controlPoint2, xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); point.endControl = _calculatePoint( @@ -382,7 +390,7 @@ void _calculatePathSeriesRegion( point.controlPoint[1].controlPoint2, xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); } @@ -396,7 +404,7 @@ void _calculatePathSeriesRegion( point.controlPoint[0].controlPoint2, xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); point.endControl = _calculatePoint( @@ -404,7 +412,7 @@ void _calculatePathSeriesRegion( point.controlPoint[1].controlPoint2, xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); } @@ -501,7 +509,6 @@ void _calculatePathSeriesRegion( _chartState._requireInvertedAxis, seriesRenderer._series, rect); - if (seriesRenderer._seriesType == 'splinerangearea' && pointIndex != 0 && pointIndex <= seriesRenderer._dataPoints.length - 1) { @@ -514,7 +521,7 @@ void _calculatePathSeriesRegion( point.controlPointshigh[0].controlPoint2, xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); @@ -523,6 +530,31 @@ void _calculatePathSeriesRegion( point.controlPointshigh[1].controlPoint2, xAxisRenderer, yAxisRenderer, + _chartState._requireInvertedAxis, + seriesRenderer._series, + rect); + } + if (seriesRenderer._seriesType == 'splinerangearea' && + pointIndex >= 0 && + pointIndex <= seriesRenderer._dataPoints.length - 2) { + point.controlPointslow = seriesRenderer + ._drawLowControlPoints[seriesRenderer._dataPoints.indexOf(point)] + ._listControlPoints; + + point.lowStartControl = _calculatePoint( + point.controlPointslow[0].controlPoint1, + point.controlPointslow[0].controlPoint2, + xAxisRenderer, + yAxisRenderer, + seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._series, + rect); + + point.lowEndControl = _calculatePoint( + point.controlPointslow[1].controlPoint1, + point.controlPointslow[1].controlPoint2, + xAxisRenderer, + yAxisRenderer, seriesRenderer._chartState._requireInvertedAxis, seriesRenderer._series, rect); @@ -536,7 +568,7 @@ void _calculatePathSeriesRegion( point.low, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); point.highPoint = _calculatePoint( @@ -544,7 +576,7 @@ void _calculatePathSeriesRegion( point.high, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); } else if ((seriesRenderer._seriesType == 'hiloopenclose' || @@ -553,25 +585,25 @@ void _calculatePathSeriesRegion( point.close != null && point.low != null && point.high != null) { - final num center = (point.xValue + sideBySideInfo.minimum) + - (((point.xValue + sideBySideInfo.maximum) - - (point.xValue + sideBySideInfo.minimum)) / + final num center = (point.xValue + sideBySideMinimumVal) + + (((point.xValue + sideBySideMaximumVal) - + (point.xValue + sideBySideMinimumVal)) / 2); point.openPoint = _calculatePoint( - point.xValue + sideBySideInfo.minimum, + point.xValue + sideBySideMinimumVal, point.open, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); point.closePoint = _calculatePoint( - point.xValue + sideBySideInfo.maximum, + point.xValue + sideBySideMaximumVal, point.close, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); if (seriesRenderer._series.dataLabelSettings.isVisible) { @@ -580,7 +612,7 @@ void _calculatePathSeriesRegion( point.open, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); @@ -589,7 +621,7 @@ void _calculatePathSeriesRegion( point.close, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); } @@ -599,7 +631,7 @@ void _calculatePathSeriesRegion( point.high, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); @@ -608,24 +640,24 @@ void _calculatePathSeriesRegion( point.low, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); point.lowPoint = _calculatePoint( - point.xValue + sideBySideInfo.minimum, + point.xValue + sideBySideMinimumVal, point.low, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); point.highPoint = _calculatePoint( - point.xValue + sideBySideInfo.maximum, + point.xValue + sideBySideMaximumVal, point.high, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + _chartState._requireInvertedAxis, seriesRenderer._series, rect); } @@ -636,9 +668,9 @@ void _calculatePathSeriesRegion( point.upperQuartile != null && point.lowerQuartile != null && point.median != null) { - final num center = (point.xValue + sideBySideInfo.minimum) + - (((point.xValue + sideBySideInfo.maximum) - - (point.xValue + sideBySideInfo.minimum)) / + final num center = (point.xValue + sideBySideMinimumVal) + + (((point.xValue + sideBySideMaximumVal) - + (point.xValue + sideBySideMinimumVal)) / 2); point.centerMeanPoint = _calculatePoint( center, @@ -650,7 +682,7 @@ void _calculatePathSeriesRegion( rect); point.minimumPoint = _calculatePoint( - point.xValue + sideBySideInfo.minimum, + point.xValue + sideBySideMinimumVal, point.minimum, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, @@ -659,7 +691,7 @@ void _calculatePathSeriesRegion( rect); point.maximumPoint = _calculatePoint( - point.xValue + sideBySideInfo.maximum, + point.xValue + sideBySideMaximumVal, point.maximum, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, @@ -686,7 +718,7 @@ void _calculatePathSeriesRegion( rect); point.lowerQuartilePoint = _calculatePoint( - point.xValue + sideBySideInfo.minimum, + point.xValue + sideBySideMinimumVal, point.lowerQuartile, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, @@ -695,7 +727,7 @@ void _calculatePathSeriesRegion( rect); point.upperQuartilePoint = _calculatePoint( - point.xValue + sideBySideInfo.maximum, + point.xValue + sideBySideMaximumVal, point.upperQuartile, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, @@ -704,7 +736,7 @@ void _calculatePathSeriesRegion( rect); point.medianPoint = _calculatePoint( - point.xValue + sideBySideInfo.minimum, + point.xValue + sideBySideMinimumVal, point.median, seriesRenderer._xAxisRenderer, seriesRenderer._yAxisRenderer, @@ -724,7 +756,7 @@ void _calculatePathSeriesRegion( point.region = seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType.contains('candle') || seriesRenderer._seriesType.contains('boxandwhisker') - ? !seriesRenderer._chartState._requireInvertedAxis + ? !_chartState._requireInvertedAxis ? Rect.fromLTWH( point.markerPoint.x, point.markerPoint.y, @@ -742,9 +774,9 @@ void _calculatePathSeriesRegion( point.markerPoint2.y); if (seriesRenderer._seriesType.contains('boxandwhisker')) { point.boxRectRegion = _calculateRectangle( - point.xValue + sideBySideInfo.minimum, + point.xValue + sideBySideMinimumVal, point.upperQuartile, - point.xValue + sideBySideInfo.maximum, + point.xValue + sideBySideMaximumVal, point.lowerQuartile, seriesRenderer, _chartState); @@ -774,12 +806,13 @@ void _calculateTooltipRegion( final SfCartesianChart chart = _chartState._chart; final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; final CartesianSeries series = seriesRenderer._series; - final num crossesAt = - _getCrossesAtValue(seriesRenderer, seriesRenderer._chartState); + final num crossesAt = _getCrossesAtValue(seriesRenderer, _chartState); if ((series.enableTooltip != null || - seriesRenderer._chart.trackballBehavior != null) && + seriesRenderer._chart.trackballBehavior != null || + chart.onPointTapped != null) && (series.enableTooltip || - seriesRenderer._chart.trackballBehavior.enable) && + seriesRenderer._chart.trackballBehavior.enable || + chart.onPointTapped != null) && point != null && !point.isGap && !point.isDrop && @@ -936,7 +969,7 @@ void _drawDashedLine( even = true; } } - if (even == false && !kIsWeb) { + if (even == false) { paint.isAntiAlias = false; canvas.drawPath( _dashPath( diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/area_painter.dart index dfda3b705..bff18e920 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/area_painter.dart @@ -57,16 +57,10 @@ class _AreaChartPainter extends CustomPainter { Offset( xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); - if (widgetNeedUpdate && - xAxisRenderer._zoomFactor == 1 && - yAxisRenderer._zoomFactor == 1 && - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= seriesIndex && - oldSeriesRenderers[seriesIndex]._seriesName == - seriesRenderer._seriesName) { - oldSeriesRenderer = oldSeriesRenderers[seriesIndex]; - } + + oldSeriesRenderer = _getOldSeriesRenderer( + chartState, seriesRenderer, seriesIndex, oldSeriesRenderers); + if (seriesRenderer._reAnimate || ((!(widgetNeedUpdate || isLegendToggled) || !chartState._oldSeriesKeys.contains(series.key)) && @@ -74,24 +68,17 @@ class _AreaChartPainter extends CustomPainter { _performLinearAnimation( chartState, xAxisRenderer._axis, canvas, animationFactor); } + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; seriesRenderer._calculateRegionData( chartState, seriesRenderer, painterKey.index, point, pointIndex); if (point.isVisible && !point.isDrop) { - _point = !seriesRenderer._reAnimate && - (series.animationDuration > 0 && - widgetNeedUpdate && - !isLegendToggled && - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderer != null && - oldSeriesRenderer._segments.isNotEmpty && - oldSeriesRenderer._segments[0] is AreaSegment && - oldSeriesRenderers.length - 1 >= seriesIndex && - oldSeriesRenderer._dataPoints.length - 1 >= pointIndex) - ? oldSeriesRenderer._dataPoints[pointIndex] - : null; + _point = _getOldChartPoint(chartState, seriesRenderer, AreaSegment, + seriesIndex, pointIndex, oldSeriesRenderer, oldSeriesRenderers); _oldPoint = _point != null ? _calculatePoint( _point.xValue, diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bar_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bar_painter.dart index 29f1fd5b6..1f38f6f2a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bar_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bar_painter.dart @@ -46,6 +46,10 @@ class _BarChartPainter extends CustomPainter { ? seriesRenderer._seriesAnimation.value : 1; int segmentIndex = -1; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; seriesRenderer._calculateRegionData( diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/box_and_whisker_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/box_and_whisker_painter.dart index f5b25bb18..d36e5f209 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/box_and_whisker_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/box_and_whisker_painter.dart @@ -49,9 +49,11 @@ class _BoxAndWhiskerPainter extends CustomPainter { animationFactor = seriesRenderer._seriesAnimation != null ? seriesRenderer._seriesAnimation.value : 1; - final _VisibleRange sideBySideInfo = - _calculateSideBySideInfo(seriesRenderer, chartState); int segmentIndex = -1; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; assert(point.y != null, @@ -60,7 +62,7 @@ class _BoxAndWhiskerPainter extends CustomPainter { (point.y).sort(); seriesRenderer._findBoxPlotValues(point.y, point, series.boxPlotMode); seriesRenderer._calculateRegionData(chartState, seriesRenderer, - painterKey.index, point, pointIndex, sideBySideInfo); + painterKey.index, point, pointIndex, seriesRenderer.sideBySideInfo); if (point.isVisible && !point.isGap) { seriesRenderer._drawSegment( canvas, diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bubble_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bubble_painter.dart index 0dbf1b6b8..4583a01eb 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bubble_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bubble_painter.dart @@ -44,6 +44,10 @@ class _BubbleChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); int segmentIndex = -1; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { final CartesianChartPoint currentPoint = dataPoints[pointIndex]; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/candle_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/candle_painter.dart index ac645764b..9d6cceddd 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/candle_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/candle_painter.dart @@ -46,13 +46,16 @@ class _CandlePainter extends CustomPainter { animationFactor = seriesRenderer._seriesAnimation != null ? seriesRenderer._seriesAnimation.value : 1; - final _VisibleRange sideBySideInfo = - _calculateSideBySideInfo(seriesRenderer, chartState); int segmentIndex = -1; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; seriesRenderer._calculateRegionData(chartState, seriesRenderer, - painterKey.index, point, pointIndex, sideBySideInfo); + painterKey.index, point, pointIndex, seriesRenderer.sideBySideInfo); + if (point.isVisible && !point.isGap) { seriesRenderer._drawSegment( canvas, diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/column_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/column_painter.dart index 3d9df0547..bd0825b1e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/column_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/column_painter.dart @@ -47,6 +47,10 @@ class _ColumnChartPainter extends CustomPainter { ? seriesRenderer._seriesAnimation.value : 1; int segmentIndex = -1; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; seriesRenderer._calculateRegionData( diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/fastline_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/fastline_painter.dart index ba2c356ca..d0b5ef152 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/fastline_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/fastline_painter.dart @@ -83,6 +83,10 @@ class _FastLineChartPainter extends CustomPainter { ///Eliminating nearest points CartesianChartPoint currentPoint; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < seriesRenderer._dataPoints.length; pointIndex++) { diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hilo_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hilo_painter.dart index d7520478f..ee69d3170 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hilo_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hilo_painter.dart @@ -47,13 +47,15 @@ class _HiloPainter extends CustomPainter { ? seriesRenderer._seriesAnimation.value : 1; - final _VisibleRange sideBySideInfo = - _calculateSideBySideInfo(seriesRenderer, chartState); int segmentIndex = -1; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; seriesRenderer._calculateRegionData(chartState, seriesRenderer, - painterKey.index, point, pointIndex, sideBySideInfo); + painterKey.index, point, pointIndex, seriesRenderer.sideBySideInfo); if (point.isVisible && !point.isGap && (!((point.low == point.high) && diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hiloopenclose_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hiloopenclose_painter.dart index 911b3ca09..aca50a9ff 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hiloopenclose_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hiloopenclose_painter.dart @@ -47,13 +47,16 @@ class _HiloOpenClosePainter extends CustomPainter { ? seriesRenderer._seriesAnimation.value : 1; - final _VisibleRange sideBySideInfo = - _calculateSideBySideInfo(seriesRenderer, chartState); int segmentIndex = -1; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; seriesRenderer._calculateRegionData(chartState, seriesRenderer, - painterKey.index, point, pointIndex, sideBySideInfo); + painterKey.index, point, pointIndex, seriesRenderer.sideBySideInfo); + if (point.isVisible && !point.isGap && (!((point.low == point.high) && diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/histogram_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/histogram_painter.dart index bee14e012..220ce34de 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/histogram_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/histogram_painter.dart @@ -52,19 +52,25 @@ class _HistogramChartPainter extends CustomPainter { : 1; /// side by side range calculated - final _VisibleRange sideBySideInfo = - _calculateSideBySideInfo(seriesRenderer, chartState); int segmentIndex = -1; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; seriesRenderer._calculateRegionData(chartState, seriesRenderer, - painterKey.index, point, pointIndex, sideBySideInfo); + painterKey.index, point, pointIndex, seriesRenderer.sideBySideInfo); if (point.isVisible && !point.isGap) { seriesRenderer._drawSegment( canvas, - seriesRenderer._createSegments(point, segmentIndex += 1, - sideBySideInfo, painterKey.index, animationFactor)); + seriesRenderer._createSegments( + point, + segmentIndex += 1, + seriesRenderer.sideBySideInfo, + painterKey.index, + animationFactor)); } } if (series.showNormalDistributionCurve) { diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/line_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/line_painter.dart index 63748a643..fe399a5ad 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/line_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/line_painter.dart @@ -57,6 +57,10 @@ class _LineChartPainter extends CustomPainter { startPoint, endPoint; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { currentPoint = dataPoints[pointIndex]; seriesRenderer._calculateRegionData( diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_area_painter.dart index 2822bcdca..c749b0e1b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_area_painter.dart @@ -24,11 +24,13 @@ class _RangeAreaChartPainter extends CustomPainter { Rect clipRect; final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + CartesianSeriesRenderer oldSeriesRenderer; final List> dataPoints = seriesRenderer._dataPoints; - CartesianChartPoint point, prevPoint; + CartesianChartPoint point, prevPoint, oldPoint; final Path _path = Path(); - _ChartLocation currentPointLow, currentPointHigh; + _ChartLocation currentPointLow, currentPointHigh, oldPointLow, oldPointHigh; + num currentLowX, currentLowY, currentHighX, currentHighY; double animationFactor; final Path _borderPath = Path(); RangeAreaSegment rangeAreaSegment; @@ -39,6 +41,8 @@ class _RangeAreaChartPainter extends CustomPainter { ? series.animationDuration >= 0 : true, 'The animation duration of the range area series must be greater or equal to 0.'); + final List oldSeriesRenderers = + chartState._oldSeriesRenderers; canvas.save(); final int seriesIndex = painterKey.index; seriesRenderer._storeSeriesProperties(chartState, seriesIndex); @@ -48,6 +52,10 @@ class _RangeAreaChartPainter extends CustomPainter { Offset( xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); + + oldSeriesRenderer = _getOldSeriesRenderer( + chartState, seriesRenderer, seriesIndex, oldSeriesRenderers); + animationFactor = seriesRenderer._seriesAnimation != null ? seriesRenderer._seriesAnimation.value : 1; @@ -58,34 +66,89 @@ class _RangeAreaChartPainter extends CustomPainter { _performLinearAnimation( chartState, xAxisRenderer._axis, canvas, animationFactor); } + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; seriesRenderer._calculateRegionData( chartState, seriesRenderer, painterKey.index, point, pointIndex); if (point.isVisible && !point.isDrop) { + oldPoint = _getOldChartPoint( + chartState, + seriesRenderer, + RangeAreaSegment, + seriesIndex, + pointIndex, + oldSeriesRenderer, + oldSeriesRenderers); + if (oldPoint != null) { + oldPointLow = _calculatePoint( + oldPoint.xValue, + oldPoint.low, + oldSeriesRenderer._xAxisRenderer, + oldSeriesRenderer._yAxisRenderer, + isTransposed, + oldSeriesRenderer._series, + axisClipRect); + oldPointHigh = _calculatePoint( + oldPoint.xValue, + oldPoint.high, + oldSeriesRenderer._xAxisRenderer, + oldSeriesRenderer._yAxisRenderer, + isTransposed, + oldSeriesRenderer._series, + axisClipRect); + } else { + oldPointLow = oldPointHigh = null; + } currentPointLow = _calculatePoint(point.xValue, point.low, xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); currentPointHigh = _calculatePoint(point.xValue, point.high, xAxisRenderer, yAxisRenderer, 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, seriesRenderer); + } else { + currentLowY = _getAnimateValue(animationFactor, currentLowY, + oldPointLow.y, currentPointLow.y, seriesRenderer); + } + } + if (oldPointHigh != null) { + if (chart.isTransposed) { + currentHighX = _getAnimateValue(animationFactor, currentHighX, + oldPointHigh.x, currentPointHigh.x, seriesRenderer); + } else { + currentHighY = _getAnimateValue(animationFactor, currentHighY, + oldPointHigh.y, currentPointHigh.y, seriesRenderer); + } + } if (prevPoint == null || dataPoints[pointIndex - 1].isGap == true || (dataPoints[pointIndex].isGap == true) || (dataPoints[pointIndex - 1].isVisible == false && series.emptyPointSettings.mode == EmptyPointMode.gap)) { - _path.moveTo(currentPointLow.x, currentPointLow.y); - _path.lineTo(currentPointHigh.x, currentPointHigh.y); - _borderPath.moveTo(currentPointHigh.x, currentPointHigh.y); + _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(currentPointHigh.x, currentPointHigh.y); - _path.lineTo(currentPointLow.x, currentPointLow.y); - _borderPath.lineTo(currentPointHigh.x, currentPointHigh.y); - _borderPath.moveTo(currentPointLow.x, currentPointLow.y); + _path.lineTo(currentHighX, currentHighY); + _path.lineTo(currentLowX, currentLowY); + _borderPath.lineTo(currentHighX, currentHighY); + _borderPath.moveTo(currentLowX, currentLowY); } else { - _borderPath.lineTo(currentPointHigh.x, currentPointHigh.y); - _path.lineTo(currentPointHigh.x, currentPointHigh.y); + _borderPath.lineTo(currentHighX, currentHighY); + _path.lineTo(currentHighX, currentHighY); } prevPoint = point; } @@ -99,22 +162,73 @@ class _RangeAreaChartPainter extends CustomPainter { pointIndex--) { point = dataPoints[pointIndex]; if (point.isVisible && !point.isDrop) { + oldPoint = _getOldChartPoint( + chartState, + seriesRenderer, + RangeAreaSegment, + seriesIndex, + pointIndex, + oldSeriesRenderer, + oldSeriesRenderers); + if (oldPoint != null) { + oldPointLow = _calculatePoint( + oldPoint.xValue, + oldPoint.low, + oldSeriesRenderer._xAxisRenderer, + oldSeriesRenderer._yAxisRenderer, + isTransposed, + oldSeriesRenderer._series, + axisClipRect); + oldPointHigh = _calculatePoint( + oldPoint.xValue, + oldPoint.high, + oldSeriesRenderer._xAxisRenderer, + oldSeriesRenderer._yAxisRenderer, + isTransposed, + oldSeriesRenderer._series, + axisClipRect); + } else { + oldPointLow = oldPointHigh = null; + } currentPointLow = _calculatePoint(point.xValue, point.low, xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); currentPointHigh = _calculatePoint(point.xValue, point.high, xAxisRenderer, yAxisRenderer, 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, seriesRenderer); + } else { + currentLowY = _getAnimateValue(animationFactor, currentLowY, + oldPointLow.y, currentPointLow.y, seriesRenderer); + } + } + if (oldPointHigh != null) { + if (chart.isTransposed) { + currentHighX = _getAnimateValue(animationFactor, currentHighX, + oldPointHigh.x, currentPointHigh.x, seriesRenderer); + } else { + currentHighY = _getAnimateValue(animationFactor, currentHighY, + oldPointHigh.y, currentPointHigh.y, seriesRenderer); + } + } if (dataPoints[pointIndex + 1].isGap == true) { - _borderPath.moveTo(currentPointLow.x, currentPointLow.y); - _path.moveTo(currentPointLow.x, currentPointLow.y); + _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(currentPointLow.x, currentPointLow.y); + _borderPath.moveTo(currentLowX, currentLowY); } else { - _borderPath.lineTo(currentPointLow.x, currentPointLow.y); + _borderPath.lineTo(currentLowX, currentLowY); } - _path.lineTo(currentPointLow.x, currentPointLow.y); + _path.lineTo(currentLowX, currentLowY); } prevPoint = point; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_column_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_column_painter.dart index c52a61ae8..58f416f79 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_column_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_column_painter.dart @@ -48,6 +48,10 @@ class _RangeColumnChartPainter extends CustomPainter { : 1; int segmentIndex = -1; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; seriesRenderer._calculateRegionData( diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/scatter_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/scatter_painter.dart index 5bf3b8e8a..1736bba09 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/scatter_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/scatter_painter.dart @@ -44,6 +44,10 @@ class _ScatterChartPainter extends CustomPainter { final int seriesIndex = painterKey.index; seriesRenderer._storeSeriesProperties(chartState, seriesIndex); int segmentIndex = -1; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { final CartesianChartPoint currentPoint = dataPoints[pointIndex]; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_area_painter.dart index 658de9786..ffb3c6495 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_area_painter.dart @@ -21,8 +21,9 @@ class _SplineAreaChartPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { double animationFactor; - CartesianChartPoint prevPoint, point; - _ChartLocation currentPoint, originPoint; + CartesianChartPoint prevPoint, point, oldChartPoint; + CartesianSeriesRenderer oldSeriesRenderer; + _ChartLocation currentPoint, originPoint, oldPointLocation; final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; Rect clipRect; @@ -33,12 +34,15 @@ class _SplineAreaChartPainter extends CustomPainter { final List _points = []; final num crossesAt = _getCrossesAtValue(seriesRenderer, chartState); final num origin = crossesAt ?? 0; + num startControlX, startControlY, endControlX, endControlY; if (seriesRenderer._visible) { assert( series.animationDuration != null ? series.animationDuration >= 0 : true, 'The animation duration of the spline area series must be greater or equal to 0.'); + final List oldSeriesRenderers = + chartState._oldSeriesRenderers; final List> dataPoints = seriesRenderer._dataPoints; canvas.save(); @@ -53,6 +57,10 @@ class _SplineAreaChartPainter extends CustomPainter { animationFactor = seriesRenderer._seriesAnimation != null ? seriesRenderer._seriesAnimation.value : 1; + + oldSeriesRenderer = _getOldSeriesRenderer( + chartState, seriesRenderer, seriesIndex, oldSeriesRenderers); + if (seriesRenderer._reAnimate || ((!(chartState._widgetNeedUpdate || chartState._isLegendToggled) || !chartState._oldSeriesKeys.contains(series.key)) && @@ -60,13 +68,38 @@ class _SplineAreaChartPainter extends CustomPainter { _performLinearAnimation( chartState, xAxisRenderer._axis, canvas, animationFactor); } - _calculateSplineAreaControlPoints(seriesRenderer); + if (!seriesRenderer._hasDataLabelTemplate) { + _calculateSplineAreaControlPoints(seriesRenderer); + } + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; seriesRenderer._calculateRegionData( chartState, seriesRenderer, painterKey.index, point, pointIndex); if (point.isVisible && !point.isDrop) { + //Stores the old data point details of the corresponding point index + oldChartPoint = _getOldChartPoint( + chartState, + seriesRenderer, + SplineAreaSegment, + seriesIndex, + pointIndex, + oldSeriesRenderer, + oldSeriesRenderers); + oldPointLocation = oldChartPoint != null + ? _calculatePoint( + oldChartPoint.xValue, + oldChartPoint.yValue, + oldSeriesRenderer._xAxisRenderer, + oldSeriesRenderer._yAxisRenderer, + isTransposed, + oldSeriesRenderer._series, + axisClipRect) + : null; currentPoint = _calculatePoint(point.xValue, point.yValue, xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); originPoint = _calculatePoint( @@ -77,13 +110,63 @@ class _SplineAreaChartPainter extends CustomPainter { isTransposed, series, axisClipRect); - final num x = currentPoint.x; - final num y = currentPoint.y; + num x = currentPoint.x; + num 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) : false; + + //calculates animation values for control points and data points + if (oldPointLocation != null) { + if (isTransposed) { + x = _getAnimateValue(animationFactor, x, oldPointLocation.x, + currentPoint.x, seriesRenderer); + } else { + y = _getAnimateValue(animationFactor, y, oldPointLocation.y, + currentPoint.y, seriesRenderer); + } + if (point.startControl != null) { + startControlY = _getAnimateValue( + animationFactor, + startControlY, + oldChartPoint.startControl.y, + point.startControl.y, + seriesRenderer); + startControlX = _getAnimateValue( + animationFactor, + startControlX, + oldChartPoint.startControl.x, + point.startControl.x, + seriesRenderer); + } + if (point.endControl != null) { + endControlX = _getAnimateValue( + animationFactor, + endControlX, + oldChartPoint.endControl.x, + point.endControl.x, + seriesRenderer); + endControlY = _getAnimateValue( + animationFactor, + endControlY, + oldChartPoint.endControl.y, + point.endControl.y, + seriesRenderer); + } + } 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) || @@ -102,41 +185,31 @@ class _SplineAreaChartPainter extends CustomPainter { _path.lineTo(x, y); } else if (pointIndex == dataPoints.length - 1 || dataPoints[pointIndex + 1].isGap == true) { - _strokePath.cubicTo(point.startControl.x, point.startControl.y, - point.endControl.x, point.endControl.y, x, y); + _strokePath.cubicTo( + startControlX, startControlY, endControlX, endControlY, x, y); if (series.borderDrawMode == BorderDrawMode.excludeBottom) { _strokePath.lineTo(originPoint.x, originPoint.y); } else if (series.borderDrawMode == BorderDrawMode.all) { _strokePath.lineTo(originPoint.x, originPoint.y); _strokePath.close(); } - _path.cubicTo(point.startControl.x, point.startControl.y, - point.endControl.x, point.endControl.y, x, y); + _path.cubicTo( + startControlX, startControlY, endControlX, endControlY, x, y); _path.lineTo(originPoint.x, originPoint.y); } else { - _strokePath.cubicTo(point.startControl.x, point.startControl.y, - point.endControl.x, point.endControl.y, x, y); - _path.cubicTo(point.startControl.x, point.startControl.y, - point.endControl.x, point.endControl.y, x, y); + _strokePath.cubicTo( + startControlX, startControlY, endControlX, endControlY, x, y); + _path.cubicTo( + startControlX, startControlY, endControlX, endControlY, x, y); if (closed) { - _path.cubicTo( - point.startControl.x, - point.startControl.y, - point.endControl.x, - point.endControl.y, - originPoint.x, - originPoint.y); + _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( - point.startControl.x, - point.startControl.y, - point.endControl.x, - point.endControl.y, - originPoint.x, - originPoint.y); + _strokePath.cubicTo(startControlX, startControlY, endControlX, + endControlY, originPoint.x, originPoint.y); _strokePath.close(); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_painter.dart index 32e9999f1..b82f05e01 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_painter.dart @@ -54,12 +54,19 @@ class _SplineChartPainter extends CustomPainter { _performLinearAnimation( chartState, xAxisRenderer._axis, canvas, animationFactor); } - _calculateSplineAreaControlPoints(seriesRenderer); + if (!seriesRenderer._hasDataLabelTemplate) { + _calculateSplineAreaControlPoints(seriesRenderer); + } int segmentIndex = -1; CartesianChartPoint point, _nextPoint, startPoint, endPoint; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } + ///Draw spline for spline series for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_range_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_range_area_painter.dart index 897ea965f..aa1f7e7e5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_range_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_range_area_painter.dart @@ -22,19 +22,27 @@ class _SplineRangeAreaChartPainter extends CustomPainter { void paint(Canvas canvas, Size size) { Rect clipRect; double animationFactor; - _ChartLocation currentPointLow, - currentPointHigh, - renderPointlow, - renderPointhigh; + _ChartLocation currentPointLow, currentPointHigh, oldPointLow, oldPointHigh; final int pointsLength = seriesRenderer._dataPoints.length; - CartesianChartPoint prevPoint, point; - List<_ControlPoints> controlPointslow; + CartesianChartPoint prevPoint, point, oldChartPoint; final Path _path = Path(); final Path _strokePath = Path(); final List> dataPoints = seriesRenderer._dataPoints; - _ChartLocation renderControlPointlow1, renderControlPointlow2; + CartesianSeriesRenderer oldSeriesRenderer; final List _points = []; + final ChartAxisRenderer xAxisRenderer = seriesRenderer?._xAxisRenderer; + final ChartAxisRenderer yAxisRenderer = seriesRenderer?._yAxisRenderer; + final List oldSeriesRenderers = + chartState._oldSeriesRenderers; + num currentPointLowX, + currentPointLowY, + currentPointHighX, + currentPointHighY, + startControlX, + startControlY, + endControlX, + endControlY; seriesRenderer?._drawHighControlPoints?.clear(); seriesRenderer?._drawLowControlPoints?.clear(); @@ -42,6 +50,10 @@ class _SplineRangeAreaChartPainter extends CustomPainter { if (seriesRenderer._visible) { canvas.save(); final int seriesIndex = painterKey.index; + + oldSeriesRenderer = _getOldSeriesRenderer( + chartState, seriesRenderer, seriesIndex, oldSeriesRenderers); + seriesRenderer._storeSeriesProperties(chartState, seriesIndex); final bool isTransposed = chartState._requireInvertedAxis; final SplineRangeAreaSeries series = @@ -52,8 +64,6 @@ class _SplineRangeAreaChartPainter extends CustomPainter { : true, 'The animation duration of the spline range area series must be greater or equal to 0.'); SplineRangeAreaSegment splineRangeAreaSegment; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; final Rect axisClipRect = _calculatePlotOffset( chartState._chartAxis._axisClipRect, Offset( @@ -69,67 +79,134 @@ class _SplineRangeAreaChartPainter extends CustomPainter { _performLinearAnimation( chartState, xAxisRenderer._axis, canvas, animationFactor); } - _calculateSplineAreaControlPoints(seriesRenderer); + if (!seriesRenderer._hasDataLabelTemplate) { + _calculateSplineAreaControlPoints(seriesRenderer); + } + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < pointsLength; pointIndex++) { point = seriesRenderer._dataPoints[pointIndex]; seriesRenderer._calculateRegionData( chartState, seriesRenderer, painterKey.index, point, pointIndex); if (point.isVisible && !point.isDrop) { + oldChartPoint = _getOldChartPoint( + chartState, + seriesRenderer, + SplineRangeAreaSegment, + seriesIndex, + pointIndex, + oldSeriesRenderer, + oldSeriesRenderers); + if (oldChartPoint != null) { + oldPointHigh = _calculatePoint( + oldChartPoint.xValue, + oldChartPoint.high, + oldSeriesRenderer._xAxisRenderer, + oldSeriesRenderer._yAxisRenderer, + isTransposed, + series, + axisClipRect); + } else { + oldPointHigh = null; + } currentPointLow = _calculatePoint(point.xValue, point.low, xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); currentPointHigh = _calculatePoint(point.xValue, point.high, xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); - renderPointhigh = _calculatePoint(point.xValue, point.high, - xAxisRenderer, yAxisRenderer, 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, + seriesRenderer); + currentPointHighY = currentPointHigh.y; + } else { + currentPointHighX = currentPointHigh.x; + currentPointHighY = _getAnimateValue( + animationFactor, + currentPointHighY, + oldPointHigh.y, + currentPointHigh.y, + seriesRenderer); + } + if (point.highStartControl != null) { + startControlX = _getAnimateValue( + animationFactor, + startControlX, + oldChartPoint.highStartControl.x, + point.highStartControl.x, + seriesRenderer); + startControlY = _getAnimateValue( + animationFactor, + startControlY, + oldChartPoint.highStartControl.y, + point.highStartControl.y, + seriesRenderer); + } else { + startControlX = startControlY = null; + } + if (point.highEndControl != null) { + endControlX = _getAnimateValue( + animationFactor, + endControlX, + oldChartPoint.highEndControl.x, + point.highEndControl.x, + seriesRenderer); + endControlY = _getAnimateValue( + animationFactor, + endControlY, + oldChartPoint.highEndControl.y, + point.highEndControl.y, + seriesRenderer); + } 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(currentPointLow.x, currentPointLow.y); - _path.lineTo(currentPointHigh.x, currentPointHigh.y); - _strokePath.moveTo(currentPointHigh.x, currentPointHigh.y); + _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( - point.highStartControl.x, - point.highStartControl.y, - point.highEndControl.x, - point.highEndControl.y, - renderPointhigh.x, - renderPointhigh.y); + _path.cubicTo(startControlX, startControlY, endControlX, + endControlY, currentPointHighX, currentPointHighY); - _strokePath.cubicTo( - point.highStartControl.x, - point.highStartControl.y, - point.highEndControl.x, - point.highEndControl.y, - renderPointhigh.x, - renderPointhigh.y); + _strokePath.cubicTo(startControlX, startControlY, endControlX, + endControlY, currentPointHighX, currentPointHighY); - _path.lineTo(currentPointLow.x, currentPointLow.y); + _path.lineTo(currentPointLowX, currentPointLowY); - _strokePath.lineTo(currentPointHigh.x, currentPointHigh.y); - _strokePath.moveTo(currentPointLow.x, currentPointLow.y); + _strokePath.lineTo(currentPointHighX, currentPointHighY); + _strokePath.moveTo(currentPointLowX, currentPointLowY); } else { - _path.cubicTo( - point.highStartControl.x, - point.highStartControl.y, - point.highEndControl.x, - point.highEndControl.y, - renderPointhigh.x, - renderPointhigh.y); + _path.cubicTo(startControlX, startControlY, endControlX, + endControlY, currentPointHighX, currentPointHighY); - _strokePath.cubicTo( - point.highStartControl.x, - point.highStartControl.y, - point.highEndControl.x, - point.highEndControl.y, - renderPointhigh.x, - renderPointhigh.y); + _strokePath.cubicTo(startControlX, startControlY, endControlX, + endControlY, currentPointHighX, currentPointHighY); } prevPoint = point; @@ -145,97 +222,103 @@ class _SplineRangeAreaChartPainter extends CustomPainter { pointIndex--) { point = dataPoints[pointIndex]; if (point.isVisible && !point.isDrop) { + oldChartPoint = _getOldChartPoint( + chartState, + seriesRenderer, + SplineRangeAreaSegment, + seriesIndex, + pointIndex, + oldSeriesRenderer, + oldSeriesRenderers); + if (oldChartPoint != null) { + oldPointLow = _calculatePoint( + oldChartPoint.xValue, + oldChartPoint.low, + oldSeriesRenderer._xAxisRenderer, + oldSeriesRenderer._yAxisRenderer, + isTransposed, + series, + axisClipRect); + } else { + oldPointLow = null; + } currentPointLow = _calculatePoint(point.xValue, point.low, xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); currentPointHigh = _calculatePoint(point.xValue, point.high, xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); + if (oldPointLow != null) { + if (isTransposed) { + currentPointLowX = _getAnimateValue( + animationFactor, + currentPointLowX, + oldPointLow.x, + currentPointLow.x, + seriesRenderer); + currentPointLowY = currentPointLow.y; + } else { + currentPointLowX = currentPointLow.x; + currentPointLowY = _getAnimateValue( + animationFactor, + currentPointLowY, + oldPointLow.y, + currentPointLow.y, + seriesRenderer); + } + if (point.lowStartControl != null) { + startControlX = _getAnimateValue( + animationFactor, + startControlX, + oldChartPoint.lowStartControl.x, + point.lowStartControl.x, + seriesRenderer); + startControlY = _getAnimateValue( + animationFactor, + startControlY, + oldChartPoint.lowStartControl.y, + point.lowStartControl.y, + seriesRenderer); + } else { + startControlX = startControlY = null; + } + if (point.lowEndControl != null) { + endControlX = _getAnimateValue( + animationFactor, + endControlX, + oldChartPoint.lowEndControl.x, + point.lowEndControl.x, + seriesRenderer); + endControlY = _getAnimateValue( + animationFactor, + endControlY, + oldChartPoint.lowEndControl.y, + point.lowEndControl.y, + seriesRenderer); + } 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(currentPointLow.x, currentPointLow.y); - _path.moveTo(currentPointLow.x, currentPointLow.y); + _strokePath.moveTo(currentPointLowX, currentPointLowY); + _path.moveTo(currentPointLowX, currentPointLowY); } else if (dataPoints[pointIndex].isGap != true) { if (pointIndex + 1 == dataPoints.length - 1 && dataPoints[pointIndex + 1].isDrop) { - _strokePath.moveTo(currentPointLow.x, currentPointLow.y); + _strokePath.moveTo(currentPointLowX, currentPointLowY); } else { - controlPointslow = seriesRenderer - ._drawLowControlPoints[ - seriesRenderer._dataPoints.indexOf(point)] - ._listControlPoints; - - renderPointlow = _calculatePoint( - point.xValue, - point.low, - xAxisRenderer, - yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, - series, - axisClipRect); - - renderControlPointlow1 = _calculatePoint( - controlPointslow[0].controlPoint1, - controlPointslow[0].controlPoint2, - xAxisRenderer, - yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, - series, - axisClipRect); - - renderControlPointlow2 = _calculatePoint( - controlPointslow[1].controlPoint1, - controlPointslow[1].controlPoint2, - xAxisRenderer, - yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, - series, - axisClipRect); - _strokePath.cubicTo( - renderControlPointlow2.x, - renderControlPointlow2.y, - renderControlPointlow1.x, - renderControlPointlow1.y, - renderPointlow.x, - renderPointlow.y); + _strokePath.cubicTo(endControlX, endControlY, startControlX, + startControlY, currentPointLowX, currentPointLowY); } - controlPointslow = seriesRenderer - ._drawLowControlPoints[ - seriesRenderer._dataPoints.indexOf(point)] - ._listControlPoints; - - renderPointlow = _calculatePoint( - point.xValue, - point.low, - xAxisRenderer, - yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, - series, - axisClipRect); - - renderControlPointlow1 = _calculatePoint( - controlPointslow[0].controlPoint1, - controlPointslow[0].controlPoint2, - xAxisRenderer, - yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, - series, - axisClipRect); - - renderControlPointlow2 = _calculatePoint( - controlPointslow[1].controlPoint1, - controlPointslow[1].controlPoint2, - xAxisRenderer, - yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, - series, - axisClipRect); - - _path.cubicTo( - renderControlPointlow2.x, - renderControlPointlow2.y, - renderControlPointlow1.x, - renderControlPointlow1.y, - renderPointlow.x, - renderPointlow.y); + _path.cubicTo(endControlX, endControlY, startControlX, + startControlY, currentPointLowX, currentPointLowY); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/step_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/step_area_painter.dart index 42ddb0430..9dfa51fef 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/step_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/step_area_painter.dart @@ -22,8 +22,15 @@ class _StepAreaChartPainter extends CustomPainter { void paint(Canvas canvas, Size size) { Rect clipRect; double animationFactor; - CartesianChartPoint prevPoint, point; - _ChartLocation currentPoint, originPoint, previousPoint; + CartesianChartPoint prevPoint, point, oldChartPoint; + _ChartLocation currentPoint, + originPoint, + previousPoint, + oldPoint, + prevOldPoint; + CartesianSeriesRenderer oldSeriesRenderer; + final List oldSeriesRenderers = + chartState._oldSeriesRenderers; final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; final StepAreaSeries _series = seriesRenderer._series; @@ -50,6 +57,10 @@ class _StepAreaChartPainter extends CustomPainter { Offset(seriesRenderer._xAxisRenderer._axis.plotOffset, seriesRenderer._yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); + + oldSeriesRenderer = _getOldSeriesRenderer( + chartState, seriesRenderer, seriesIndex, oldSeriesRenderers); + seriesRenderer._storeSeriesProperties(chartState, seriesIndex); animationFactor = seriesRenderer._seriesAnimation != null ? seriesRenderer._seriesAnimation.value @@ -63,11 +74,33 @@ class _StepAreaChartPainter extends CustomPainter { canvas, animationFactor); } + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; seriesRenderer._calculateRegionData( chartState, seriesRenderer, painterKey.index, point, pointIndex); if (point.isVisible) { + oldChartPoint = _getOldChartPoint( + chartState, + seriesRenderer, + StepAreaSegment, + seriesIndex, + pointIndex, + oldSeriesRenderer, + oldSeriesRenderers); + oldPoint = oldChartPoint != null + ? _calculatePoint( + oldChartPoint.xValue, + oldChartPoint.yValue, + oldSeriesRenderer._xAxisRenderer, + oldSeriesRenderer._yAxisRenderer, + chart.isTransposed, + oldSeriesRenderer._series, + axisClipRect) + : null; currentPoint = _calculatePoint( point.xValue, point.yValue, @@ -95,9 +128,20 @@ class _StepAreaChartPainter extends CustomPainter { series, axisClipRect); _points.add(Offset(currentPoint.x, currentPoint.y)); - _drawStepAreaPath(_path, _strokePath, prevPoint, currentPoint, - originPoint, previousPoint, pointIndex, _series); + _drawStepAreaPath( + _path, + _strokePath, + prevPoint, + currentPoint, + originPoint, + previousPoint, + oldPoint, + prevOldPoint, + pointIndex, + animationFactor, + _series); prevPoint = point; + prevOldPoint = oldPoint; } } @@ -149,14 +193,29 @@ class _StepAreaChartPainter extends CustomPainter { _ChartLocation currentPoint, _ChartLocation originPoint, _ChartLocation previousPoint, + _ChartLocation oldPoint, + _ChartLocation prevOldPoint, int pointIndex, + double animationFactor, StepAreaSeries stepAreaSeries) { - final num x = currentPoint.x; - final num y = currentPoint.y; + num x = currentPoint.x; + num y = currentPoint.y; + num previousPointY = previousPoint?.y; final bool closed = stepAreaSeries.emptyPointSettings.mode == EmptyPointMode.drop ? _getSeriesVisibility(seriesRenderer._dataPoints, pointIndex) : false; + if (oldPoint != null) { + if (chartState._chart.isTransposed) { + x = _getAnimateValue( + animationFactor, x, oldPoint.x, currentPoint.x, seriesRenderer); + } else { + y = _getAnimateValue( + animationFactor, y, oldPoint.y, currentPoint.y, seriesRenderer); + previousPointY = _getAnimateValue(animationFactor, previousPointY, + prevOldPoint?.y, previousPoint?.y, seriesRenderer); + } + } if (prevPoint == null || seriesRenderer._dataPoints[pointIndex - 1].isGap == true || (seriesRenderer._dataPoints[pointIndex].isGap == true) || @@ -179,7 +238,7 @@ class _StepAreaChartPainter extends CustomPainter { _path.lineTo(x, y); } else if (pointIndex == seriesRenderer._dataPoints.length - 1 || seriesRenderer._dataPoints[pointIndex + 1].isGap == true) { - _strokePath.lineTo(x, previousPoint.y); + _strokePath.lineTo(x, previousPointY); _strokePath.lineTo(x, y); if (stepAreaSeries.borderDrawMode == BorderDrawMode.excludeBottom) { _strokePath.lineTo(originPoint.x, originPoint.y); @@ -187,12 +246,12 @@ class _StepAreaChartPainter extends CustomPainter { _strokePath.lineTo(originPoint.x, originPoint.y); _strokePath.close(); } - _path.lineTo(x, previousPoint.y); + _path.lineTo(x, previousPointY); _path.lineTo(x, y); _path.lineTo(originPoint.x, originPoint.y); } else { - _path.lineTo(x, previousPoint.y); - _strokePath.lineTo(x, previousPoint.y); + _path.lineTo(x, previousPointY); + _strokePath.lineTo(x, previousPointY); _strokePath.lineTo(x, y); _path.lineTo(x, y); if (closed) { diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stepline_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stepline_painter.dart index 9ff02facd..ae9e36154 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stepline_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stepline_painter.dart @@ -58,6 +58,11 @@ class _StepLineChartPainter extends CustomPainter { _nextPoint; num midX, midY; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } + for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { currentPoint = dataPoints[pointIndex]; if ((currentPoint.isVisible && !currentPoint.isGap) && diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/waterfall_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/waterfall_painter.dart index 75cedb3f8..d1445add7 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/waterfall_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/waterfall_painter.dart @@ -52,6 +52,10 @@ class _WaterfallChartPainter extends CustomPainter { ? seriesRenderer._seriesAnimation.value : 1; int segmentIndex = -1; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; currentEndValue += diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/technical_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/technical_indicator.dart index c03f5bef5..8768bc69e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/technical_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/technical_indicator.dart @@ -447,14 +447,14 @@ class TechnicalIndicatorsRenderer { !name.contains('Line')) { final IndicatorRenderArgs indicatorArgs = IndicatorRenderArgs( indicator, _index, chart.indicators[_index].seriesName, _dataPoints); - indicatorArgs.indicatorname = name; + indicatorArgs.indicatorName = name; indicatorArgs.signalLineColor = color; indicatorArgs.signalLineWidth = width; indicatorArgs.lineDashArray = indicator.dashArray; chart.onIndicatorRender(indicatorArgs); color = indicatorArgs.signalLineColor; width = indicatorArgs.signalLineWidth; - name = indicatorArgs.indicatorname; + name = indicatorArgs.indicatorName; _dashArray = indicatorArgs.lineDashArray; } final CartesianSeries series = name == 'rangearea' diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines_painter.dart index 3786c2f55..1f785e4fa 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines_painter.dart @@ -46,9 +46,6 @@ class _TrendlinePainter extends CustomPainter { final Path path = Path(); final Paint paint = Paint(); paint.strokeWidth = trendline.width; - paint.color = trendlineRenderer._fillColor - .withOpacity(trendlineRenderer._opacity); - paint.style = PaintingStyle.stroke; if (seriesRenderer._reAnimate || (trendline.animationDuration > 0 && seriesRenderer._oldSeries == null)) { @@ -62,6 +59,9 @@ class _TrendlinePainter extends CustomPainter { chartState._chartSeries.visibleSeriesRenderers .indexOf(seriesRenderer), seriesRenderer._seriesName); + paint.color = trendlineRenderer._fillColor + .withOpacity(trendlineRenderer._opacity); + paint.style = PaintingStyle.stroke; if (trendline.type == TrendlineType.linear) { path.moveTo(trendlineRenderer._points[0].dx, trendlineRenderer._points[0].dy); @@ -137,8 +137,8 @@ class _TrendlinePainter extends CustomPainter { ? Colors.transparent : ((point.pointColorMapper != null) ? point.pointColorMapper - : trendline.markerSettings.borderColor ?? - trendlineRenderer._fillColor) + : trendlineRenderer._fillColor ?? + trendline.markerSettings.borderColor) ..strokeWidth = trendline.markerSettings.borderWidth ..style = PaintingStyle.stroke; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair.dart index d929c08ef..79047c7d9 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair.dart @@ -165,14 +165,17 @@ class CrosshairBehavior { final CrosshairBehaviorRenderer crosshairBehaviorRenderer = chartState._crosshairBehaviorRenderer; if (coordinateUnit != 'pixel') { - crosshairBehaviorRenderer - ._crosshairPainter.chartState._crosshairWithoutTouch = false; final CartesianSeriesRenderer seriesRenderer = crosshairBehaviorRenderer ._crosshairPainter.chartState._chartSeries.visibleSeriesRenderers[0]; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; final _ChartLocation location = _calculatePoint( - x is DateTime ? x.millisecondsSinceEpoch : x, + x is DateTime + ? x.millisecondsSinceEpoch + : (x is String && xAxisRenderer is CategoryAxisRenderer) + ? xAxisRenderer._labels.indexOf(x) + : x, y, - seriesRenderer._xAxisRenderer, + xAxisRenderer, seriesRenderer._yAxisRenderer, seriesRenderer._chartState._requireInvertedAxis, seriesRenderer._series, @@ -188,11 +191,8 @@ class CrosshairBehavior { crosshairBehaviorRenderer._crosshairPainter ._generateAllPoints(Offset(x.toDouble(), y)); crosshairBehaviorRenderer._crosshairPainter.canResetPath = false; - if (crosshairBehaviorRenderer - ._crosshairPainter.chartState._crosshairWithoutTouch) { - crosshairBehaviorRenderer - ._crosshairPainter.chartState._crosshairRepaintNotifier.value++; - } + crosshairBehaviorRenderer + ._crosshairPainter.chartState._crosshairRepaintNotifier.value++; } } @@ -216,6 +216,8 @@ class CrosshairBehavior { seriesRenderer._dataPoints[pointIndex].markerPoint.x, seriesRenderer._dataPoints[pointIndex].markerPoint.y)); crosshairBehaviorRenderer._crosshairPainter.canResetPath = false; + crosshairBehaviorRenderer + ._crosshairPainter.chartState._crosshairRepaintNotifier.value++; } } } @@ -232,18 +234,24 @@ class CrosshairBehavior { if (crosshairBehaviorRenderer._crosshairPainter.timer != null) { crosshairBehaviorRenderer._crosshairPainter.timer.cancel(); } - if (!shouldAlwaysShow) { - final double duration = (hideDelay == 0 && - crosshairBehaviorRenderer - ._crosshairPainter.chartState._enableDoubleTap) - ? 200 - : hideDelay; - crosshairBehaviorRenderer._crosshairPainter.timer = - Timer(Duration(milliseconds: duration.toInt()), () { - crosshairBehaviorRenderer - ._crosshairPainter.chartState._crosshairRepaintNotifier.value++; - crosshairBehaviorRenderer._crosshairPainter.canResetPath = true; - }); + if (!_chartState._isTouchUp) { + crosshairBehaviorRenderer + ._crosshairPainter.chartState._trackballRepaintNotifier.value++; + crosshairBehaviorRenderer._crosshairPainter.canResetPath = true; + } else { + if (!shouldAlwaysShow) { + final double duration = (hideDelay == 0 && + crosshairBehaviorRenderer + ._crosshairPainter.chartState._enableDoubleTap) + ? 200 + : hideDelay; + crosshairBehaviorRenderer._crosshairPainter.timer = + Timer(Duration(milliseconds: duration.toInt()), () { + crosshairBehaviorRenderer + ._crosshairPainter.chartState._crosshairRepaintNotifier.value++; + crosshairBehaviorRenderer._crosshairPainter.canResetPath = true; + }); + } } } } @@ -313,6 +321,8 @@ class CrosshairBehaviorRenderer with ChartBehavior { /// 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) { _crosshairPainter._drawCrosshairLine(canvas, paint, seriesIndex); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair_painter.dart index 446a694f8..be9a0cbf0 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair_painter.dart @@ -56,8 +56,9 @@ class _CrosshairPainter extends CustomPainter { void _drawCrosshairLine(Canvas canvas, Paint paint, int index) { if (chartState._crosshairBehaviorRenderer._position != null) { final Path dashArrayPath = Path(); - if (chart.crosshairBehavior.lineType == CrosshairLineType.horizontal || - chart.crosshairBehavior.lineType == CrosshairLineType.both) { + if ((chart.crosshairBehavior.lineType == CrosshairLineType.horizontal || + chart.crosshairBehavior.lineType == CrosshairLineType.both) && + chart.crosshairBehavior.lineWidth != 0) { dashArrayPath.moveTo(chartState._chartAxis._axisClipRect.left, chartState._crosshairBehaviorRenderer._position.dy); dashArrayPath.lineTo(chartState._chartAxis._axisClipRect.right, @@ -67,8 +68,9 @@ class _CrosshairPainter extends CustomPainter { paint, dashArrayPath) : canvas.drawPath(dashArrayPath, paint); } - if (chart.crosshairBehavior.lineType == CrosshairLineType.vertical || - chart.crosshairBehavior.lineType == CrosshairLineType.both) { + if ((chart.crosshairBehavior.lineType == CrosshairLineType.vertical || + chart.crosshairBehavior.lineType == CrosshairLineType.both) && + chart.crosshairBehavior.lineWidth != 0) { dashArrayPath.moveTo(chartState._crosshairBehaviorRenderer._position.dx, chartState._chartAxis._axisClipRect.top); dashArrayPath.lineTo(chartState._crosshairBehaviorRenderer._position.dx, @@ -573,7 +575,14 @@ class _CrosshairPainter extends CustomPainter { position.dy - (chartState._chartAxis._axisClipRect.top + axisRenderer._axis.plotOffset)); - return _getInteractiveTooltipLabel(value, axisRenderer); + String resultantString = + _getInteractiveTooltipLabel(value, axisRenderer).toString(); + if (axisRenderer._axis.interactiveTooltip.format != null) { + final String stringValue = axisRenderer._axis.interactiveTooltip.format + .replaceAll('{value}', resultantString); + resultantString = stringValue; + } + return resultantString; } /// To find the y value of crosshair @@ -588,7 +597,14 @@ class _CrosshairPainter extends CustomPainter { position.dy - (chartState._chartAxis._axisClipRect.top + axisRenderer._axis.plotOffset)); - return _getInteractiveTooltipLabel(value, axisRenderer); + String resultantString = + _getInteractiveTooltipLabel(value, axisRenderer).toString(); + if (axisRenderer._axis.interactiveTooltip.format != null) { + final String stringValue = axisRenderer._axis.interactiveTooltip.format + .replaceAll('{value}', resultantString); + resultantString = stringValue; + } + return resultantString; } /// To add the tooltip for crosshair diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/selection_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/selection_renderer.dart index c27cae7dd..c1466e340 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/selection_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/selection_renderer.dart @@ -20,6 +20,8 @@ class _SelectionRenderer { List selectedSegments; List unselectedSegments; SelectionType selectionType; + int overallPointIndex; + bool selected = false; ////Selects or deselects the specified data point in the series. /// @@ -63,6 +65,9 @@ class _SelectionRenderer { select = selectionBehaviorRenderer._selectionRenderer .isCartesianSelection( chart, seriesRender, pointIndex, seriesIndex); + selected = pointIndex != null; + overallPointIndex = + seriesRender._dataPoints[pointIndex].visiblePointIndex; } } else { _chartState._renderDatalabelRegions = []; @@ -195,28 +200,40 @@ class _SelectionRenderer { selectionArgs != null && selectionArgs.selectedColor != null ? selectionArgs.selectedColor - : selectionBehavior.selectedColor ?? - segment._defaultFillColor.color; + : _chartState._selectionArgs != null && + _chartState._selectionArgs.selectedColor != null + ? _chartState._selectionArgs.selectedColor + : selectionBehavior.selectedColor ?? + segment._defaultFillColor.color; } fillOpacity = chartEventSelection != null && selectionArgs != null && selectionArgs.selectedColor != null ? selectionArgs.selectedColor.opacity - : selectionBehavior.selectedOpacity ?? series.opacity; + : _chartState._selectionArgs != null && + _chartState._selectionArgs.selectedColor != null + ? _chartState._selectionArgs.selectedColor.opacity + : selectionBehavior.selectedOpacity ?? series.opacity; } else { if (series is CartesianSeries) { fillColor = chartEventSelection != null && selectionArgs != null && selectionArgs.unselectedColor != null ? selectionArgs.unselectedColor - : selectionBehavior.unselectedColor ?? - segment._defaultFillColor.color; + : _chartState._selectionArgs != null && + _chartState._selectionArgs.unselectedColor != null + ? _chartState._selectionArgs.unselectedColor + : selectionBehavior.unselectedColor ?? + segment._defaultFillColor.color; } fillOpacity = chartEventSelection != null && selectionArgs != null && selectionArgs.unselectedColor != null ? selectionArgs.unselectedColor.opacity - : selectionBehavior.unselectedOpacity ?? series.opacity; + : _chartState._selectionArgs != null && + _chartState._selectionArgs.unselectedColor != null + ? _chartState._selectionArgs.unselectedColor.opacity + : selectionBehavior.unselectedOpacity ?? series.opacity; } final Paint selectedFillPaint = Paint(); selectedFillPaint.color = fillColor.withOpacity(fillOpacity); @@ -251,29 +268,44 @@ class _SelectionRenderer { seriesType == 'stackedline100' || seriesType.contains('hilo') || seriesType == 'candle' || - seriesType.contains('boxandwhisker') - ? strokeColor = chartEventSelection != null + seriesType == 'boxandwhisker' + ? strokeColor = chartEventSelection != null && + selectionArgs != null && + selectionArgs.selectedColor != null ? selectionArgs.selectedColor - : selectionBehavior.selectedColor ?? - segment._defaultFillColor.color + : _chartState._selectionArgs != null && + _chartState._selectionArgs.selectedColor != null + ? _chartState._selectionArgs.selectedColor + : selectionBehavior.selectedColor ?? + segment._defaultFillColor.color : strokeColor = chartEventSelection != null && selectionArgs != null && selectionArgs.selectedBorderColor != null ? selectionArgs.selectedBorderColor - : selectionBehavior.selectedBorderColor ?? series.borderColor; + : _chartState._selectionArgs != null && + _chartState._selectionArgs.selectedBorderColor != null + ? _chartState._selectionArgs.selectedBorderColor + : selectionBehavior.selectedBorderColor ?? + series.borderColor; } strokeOpacity = chartEventSelection != null && selectionArgs != null && selectionArgs.selectedBorderColor != null ? selectionArgs.selectedBorderColor.opacity - : selectionBehavior.selectedOpacity ?? series.opacity; + : _chartState._selectionArgs != null && + _chartState._selectionArgs.selectedBorderColor != null + ? _chartState._selectionArgs.selectedBorderColor.opacity + : selectionBehavior.selectedOpacity ?? series.opacity; strokeWidth = chartEventSelection != null && selectionArgs != null && selectionArgs.selectedBorderWidth != null ? selectionArgs.selectedBorderWidth - : selectionBehavior.selectedBorderWidth ?? series.borderWidth; + : _chartState._selectionArgs != null && + _chartState._selectionArgs.selectedBorderWidth != null + ? _chartState._selectionArgs.selectedBorderWidth + : selectionBehavior.selectedBorderWidth ?? series.borderWidth; } else { if (series is CartesianSeries) { segment is LineSegment || @@ -287,24 +319,37 @@ class _SelectionRenderer { segment is BoxAndWhiskerSegment ? strokeColor = chartEventSelection != null && selectionArgs != null ? selectionArgs.unselectedColor - : selectionBehavior.unselectedColor ?? - segment._defaultFillColor.color + : _chartState._selectionArgs != null && + _chartState._selectionArgs.unselectedColor != null + ? _chartState._selectionArgs.unselectedColor + : selectionBehavior.unselectedColor ?? + segment._defaultFillColor.color : strokeColor = chartEventSelection != null && selectionArgs != null && selectionArgs.unselectedBorderColor != null ? selectionArgs.unselectedBorderColor - : selectionBehavior.unselectedBorderColor ?? series.borderColor; + : _chartState._selectionArgs != null && + _chartState._selectionArgs.unselectedBorderColor != null + ? _chartState._selectionArgs.unselectedBorderColor + : selectionBehavior.unselectedBorderColor ?? + series.borderColor; } strokeOpacity = chartEventSelection != null && selectionArgs != null && selectionArgs.unselectedColor != null ? selectionArgs.unselectedColor.opacity - : selectionBehavior.unselectedOpacity ?? series.opacity; + : _chartState._selectionArgs != null && + _chartState._selectionArgs.unselectedColor != null + ? _chartState._selectionArgs.unselectedColor.opacity + : selectionBehavior.unselectedOpacity ?? series.opacity; strokeWidth = chartEventSelection != null && selectionArgs != null && selectionArgs.unselectedBorderWidth != null ? selectionArgs.unselectedBorderWidth - : selectionBehavior.unselectedBorderWidth ?? series.borderWidth; + : _chartState._selectionArgs != null && + _chartState._selectionArgs.unselectedBorderWidth != null + ? _chartState._selectionArgs.unselectedBorderWidth + : selectionBehavior.unselectedBorderWidth ?? series.borderWidth; } final Paint selectedStrokePaint = Paint(); selectedStrokePaint.color = strokeColor; @@ -545,9 +590,15 @@ class _SelectionRenderer { chart = chartAssign; seriesRenderer = seriesAssign; - if (chart.onSelectionChanged != null) { - chart.onSelectionChanged(getSelectionEventArgs( - seriesRenderer._series, seriesIndex, pointIndex, seriesRenderer)); + if (chart.onSelectionChanged != null && selected) { + chart.onSelectionChanged(getSelectionEventArgs(seriesRenderer._series, + seriesIndex, overallPointIndex, seriesRenderer)); + selected = false; + } + + /// Maintained the event arguments on zooming, device orientation change. + if (selectionArgs != null) { + _chartState._selectionArgs = selectionArgs; } /// For point mode @@ -584,8 +635,10 @@ class _SelectionRenderer { /// To identify that tapped segment in any one of the selected segment if (selectedSegment != null) { for (int k = 0; k < selectedSegments.length; k++) { - if (selectedSegment._currentPoint == - selectedSegments[k]._currentPoint) { + if (selectedSegment.currentSegmentIndex == + selectedSegments[k].currentSegmentIndex && + selectedSegment._seriesIndex == + selectedSegments[k]._seriesIndex) { multiSelect = true; break; } @@ -601,8 +654,10 @@ class _SelectionRenderer { ._segments[selectedSegments[j].currentSegmentIndex]; /// Applying default settings when last selected segment becomes unselected - if ((selectedSegment._currentPoint == - selectedSegments[j]._currentPoint) && + if ((selectedSegment.currentSegmentIndex == + selectedSegments[j].currentSegmentIndex && + selectedSegment._seriesIndex == + selectedSegments[j]._seriesIndex) && (selectedSegments.length == 1)) { final Paint fillPaint = getDefaultFillColor(null, null, currentSegment); @@ -619,8 +674,10 @@ class _SelectionRenderer { } /// Applying unselected color for unselected segments in multiSelect option - else if (selectedSegment._currentPoint == - selectedSegments[j]._currentPoint) { + else if (selectedSegment.currentSegmentIndex == + selectedSegments[j].currentSegmentIndex && + selectedSegment._seriesIndex == + selectedSegments[j]._seriesIndex) { final Paint fillPaint = getFillColor(false, currentSegment); currentSegment.fillPaint = fillPaint; final Paint strokePaint = getStrokeColor(false, currentSegment); @@ -637,6 +694,7 @@ class _SelectionRenderer { } } } else { + unselectedSegments?.clear(); isSamePointSelect = changeColorAndPopSelectedSegments( selectedSegments, isSamePointSelect); } @@ -680,6 +738,8 @@ class _SelectionRenderer { _selectedSegmentsColors(selectedSegments); } } + } else { + isSelected = true; } } } @@ -696,7 +756,9 @@ class _SelectionRenderer { for (int k = 0; k < seriesRenderer._segments.length; k++) { currentSegment = seriesRenderer._segments[k]; final ChartSegment compareSegment = seriesRenderer._segments[k]; - if (currentSegment._segmentRect != compareSegment._segmentRect) { + if (currentSegment.currentSegmentIndex != + compareSegment.currentSegmentIndex && + currentSegment._seriesIndex != compareSegment._seriesIndex) { isSelected = false; } } @@ -938,6 +1000,8 @@ class _SelectionRenderer { } } } else { + unselectedSegments?.clear(); + ///Executes when multiSelect is not enable isSamePointSelect = changeColorAndPopSelectedSegments( selectedSegments, isSamePointSelect); @@ -990,6 +1054,8 @@ class _SelectionRenderer { /// Giving Color to selected segments _selectedSegmentsColors(selectedSegments); } + } else { + isSelected = true; } } } @@ -1005,7 +1071,9 @@ class _SelectionRenderer { for (int k = 0; k < seriesRenderer._segments.length; k++) { currentSegment = seriesRenderer._segments[k]; if (currentSegment._segmentRect.contains(position)) { + selected = true; selectedSegment = seriesRenderer._segments[k]; + overallPointIndex = selectedSegment?._currentPoint?.visiblePointIndex; } } if (selectedSegment == null) { @@ -1129,6 +1197,7 @@ class _SelectionRenderer { for (final CartesianChartPoint dataPoint in nearestDataPoints) { dataPointIndex = seriesRenderer._dataPoints.indexOf(dataPoint); + overallPointIndex = dataPoint.visiblePointIndex; previousIndex = seriesRenderer._dataPoints.indexOf(dataPoint) - 1; previousIndex < 0 ? previousDataPointIndex = dataPointIndex @@ -1302,6 +1371,7 @@ class _SelectionRenderer { : false; if (isSelect) { cartesianPointIndex = getCartesianPointIndex(position); + selected = cartesianPointIndex != null; select = seriesRenderer._selectionBehaviorRenderer._selectionRenderer .isCartesianSelection(chart, seriesRenderer, cartesianPointIndex, cartesianSeriesIndex); @@ -1359,7 +1429,7 @@ class _SelectionRenderer { num pointIndex, CartesianSeriesRenderer seriesRender) { if (series != null) { selectionArgs = SelectionArgs(seriesRenderer, seriesIndex, pointIndex, - seriesRender._dataPoints[pointIndex].overallDataPointIndex); + seriesRender._visibleDataPoints[pointIndex].overallDataPointIndex); final dynamic selectionBehavior = seriesRenderer._selectionBehavior; selectionArgs.selectedBorderColor = selectionBehavior.selectedBorderColor; selectionArgs.unselectedBorderColor = diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_painter.dart index 05528ee45..5b5efd931 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_painter.dart @@ -139,11 +139,11 @@ class _TooltipPainter extends CustomPainter { []; String header; Rect boundaryRect = const Rect.fromLTWH(0, 0, 0, 0); + Rect _tooltipRect; dynamic tooltip; dynamic _chartState; - dynamic currentSeries; + dynamic currentSeries, dataPoint; num pointIndex; - DataMarkerType markerType; double markerSize; Color markerColor; CartesianSeriesRenderer seriesRenderer; @@ -238,8 +238,10 @@ class _TooltipPainter extends CustomPainter { tooltipBehaviorRenderer._painter.prevTooltipValue = TooltipValue(i, count, outlierTooltipIndex); currentSeries = seriesRenderer; - pointIndex = count; - markerType = seriesRenderer._series.markerSettings.shape; + pointIndex = _chart is SfCartesianChart + ? regionRect[4].visiblePointIndex + : count; + dataPoint = regionRect[4]; Color seriesColor = seriesRenderer._seriesColor; if (seriesRenderer._seriesType == 'waterfall') { seriesColor = _getWaterfallSeriesColor(seriesRenderer._series, @@ -251,29 +253,15 @@ class _TooltipPainter extends CustomPainter { tooltipPosition = (outlierTooltipIndex >= 0) ? regionRect[6][outlierTooltipIndex] : regionRect[1]; - if (seriesRenderer._seriesType == 'bubble' && !isTrendLine) { - padding = Offset(region.center.dx - region.centerLeft.dx, - 2 * (region.center.dy - region.topCenter.dy)); - tooltipPosition = Offset(tooltipPosition.dx, paddedRegion.top); - } else if (seriesRenderer._seriesType == 'scatter') { - padding = Offset(seriesRenderer._series.markerSettings.width, - seriesRenderer._series.markerSettings.height / 2); - tooltipPosition = - Offset(tooltipPosition.dx, tooltipPosition.dy); - } else if (seriesRenderer._seriesType.contains('rangearea')) { - padding = Offset(seriesRenderer._series.markerSettings.width, - seriesRenderer._series.markerSettings.height / 2); - tooltipPosition = - Offset(tooltipPosition.dx, tooltipPosition.dy); - } else { - padding = (seriesRenderer._series.markerSettings.isVisible) - ? Offset( - seriesRenderer._series.markerSettings.width / 2, - seriesRenderer._series.markerSettings.height / 2 + - seriesRenderer._series.markerSettings.borderWidth / - 2) - : const Offset(2, 2); - } + final List paddingData = !(seriesRenderer._isRectSeries && + tooltipBehaviorRenderer + ._tooltipBehavior.tooltipPosition != + TooltipPosition.auto) + ? _getTooltipPaddingData(seriesRenderer, isTrendLine, region, + paddedRegion, tooltipPosition) + : [const Offset(2, 2), tooltipPosition]; + padding = paddingData[0]; + tooltipPosition = paddingData[1]; dataValues = values; dataRect = regionRect; isContains = mouseTooltip = true; @@ -538,6 +526,9 @@ class _TooltipPainter extends CustomPainter { chartPoint.center); currentSeries = pointRegion.seriesIndex; pointIndex = pointRegion.pointIndex; + dataPoint = _chartState + ._chartSeries.visibleSeriesRenderers[0]._dataPoints[pointIndex]; + final int digits = chart.tooltipBehavior.decimalPlaces; String header = chart.tooltipBehavior.header; header = (header == null) // ignore: prefer_if_null_operators @@ -555,7 +546,7 @@ class _TooltipPainter extends CustomPainter { if (chart.tooltipBehavior.format != null) { final String resultantString = chart.tooltipBehavior.format .replaceAll('point.x', chartPoint.x.toString()) - .replaceAll('point.y', chartPoint.y.toString()) + .replaceAll('point.y', _getDecimalLabelValue(chartPoint.y, digits)) .replaceAll( 'series.name', _chartState @@ -570,7 +561,9 @@ class _TooltipPainter extends CustomPainter { ._calculateLocation(location); } else { _chartState._tooltipBehaviorRenderer._painter.stringValue = - chartPoint.x.toString() + ' : ' + chartPoint.y.toString(); + (chartPoint.x.toString() + + ' : ' + + _getDecimalLabelValue(chartPoint.y, digits)); _chartState._tooltipBehaviorRenderer._painter ._calculateLocation(location); } @@ -579,6 +572,7 @@ class _TooltipPainter extends CustomPainter { chart.tooltipBehavior.hide(); isContains = false; } + mouseTooltip = isContains; if (!isContains) { tooltipBehaviorRenderer._painter.prevTooltipValue = tooltipBehaviorRenderer._painter.currentTooltipValue = null; @@ -597,7 +591,10 @@ class _TooltipPainter extends CustomPainter { const num seriesIndex = 0; pointIndex = _chartState._tooltipPointIndex ?? _chartState._currentActive.pointIndex; + dataPoint = _chartState + ._chartSeries.visibleSeriesRenderers[0]._dataPoints[pointIndex]; _chartState._tooltipPointIndex = null; + final int digits = chart.tooltipBehavior.decimalPlaces; if (chart.tooltipBehavior.enable) { tooltipBehaviorRenderer._painter.prevTooltipValue = TooltipValue(seriesIndex, pointIndex); @@ -635,7 +632,7 @@ class _TooltipPainter extends CustomPainter { if (chart.tooltipBehavior.format != null) { final String resultantString = chart.tooltipBehavior.format .replaceAll('point.x', chartPoint.x.toString()) - .replaceAll('point.y', chartPoint.y.toString()) + .replaceAll('point.y', _getDecimalLabelValue(chartPoint.y, digits)) .replaceAll( 'series.name', _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] @@ -647,7 +644,9 @@ class _TooltipPainter extends CustomPainter { ._calculateLocation(location); } else { _chartState._tooltipBehaviorRenderer._painter.stringValue = - chartPoint.x.toString() + ' : ' + chartPoint.y.toString(); + chartPoint.x.toString() + + ' : ' + + _getDecimalLabelValue(chartPoint.y, digits); _chartState._tooltipBehaviorRenderer._painter ._calculateLocation(location); } @@ -656,6 +655,7 @@ class _TooltipPainter extends CustomPainter { chart.tooltipBehavior.hide(); isContains = false; } + mouseTooltip = isContains; if (!isContains) { chartState._tooltipBehaviorRenderer._painter._painter.prevTooltipValue = chartState._tooltipBehaviorRenderer._painter._painter @@ -692,24 +692,29 @@ class _TooltipPainter extends CustomPainter { isRight = false; double height = 0, width = 0, headerTextWidth = 0, headerTextHeight = 0; TooltipArgs tooltipArgs; + Size textSize, headerSize; markerSize = 0; - if (x != null && y != null && stringValue != null) { - final int seriesIndex = _chartState - ._tooltipBehaviorRenderer._painter.currentTooltipValue.seriesIndex; - final dynamic dataPoint = _chartState._chartSeries - .visibleSeriesRenderers[seriesIndex]._dataPoints[pointIndex]; - + if (x != null && + y != null && + stringValue != null && + (!(_chart is SfCartesianChart) || !_chartState._requireAxisTooltip)) { + final int seriesIndex = _chart is SfCartesianChart + ? currentSeries._segments[0]._seriesIndex + : currentSeries; if (_chart.onTooltipRender != null && !dataPoint.isTooltipRenderEvent) { dataPoint.isTooltipRenderEvent = true; - const num index = 0; tooltipArgs = TooltipArgs( _chart is SfCartesianChart - ? currentSeries._segments[index]._seriesIndex + ? currentSeries._segments[0]._seriesIndex : currentSeries, _chartState ._chartSeries.visibleSeriesRenderers[seriesIndex]._dataPoints, - pointIndex); + pointIndex, + _chart is SfCartesianChart + ? _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] + ._visibleDataPoints[pointIndex].overallDataPointIndex + : pointIndex); tooltipArgs.text = stringValue; tooltipArgs.header = header; tooltipArgs.locationX = x; @@ -721,64 +726,22 @@ class _TooltipPainter extends CustomPainter { y = tooltipArgs.locationY; } if (tooltipAnimation.status == AnimationStatus.completed) { - dataPoint.isTooltipRenderEvent = false; + dataPoint?.isTooltipRenderEvent = false; } } totalWidth = boundaryRect.left.toDouble() + boundaryRect.width.toDouble(); - //ignore: prefer_final_locals - TextStyle _textStyle = tooltip.textStyle; - final TextStyle textStyle = TextStyle( - color: _textStyle.color ?? _chartState._chartTheme.tooltipLabelColor, - fontSize: _textStyle.fontSize, - fontFamily: _textStyle.fontFamily, - fontWeight: _textStyle.fontWeight, - 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); - width = _measureText(stringValue, textStyle).width; - height = _measureText(stringValue, textStyle).height; + final TextStyle textStyle = + _getTooltiptextStyle(tooltip.textStyle, false, true); + textSize = _measureText(stringValue, textStyle); + width = textSize.width; + height = textSize.height; if (header != null && header.isNotEmpty) { - final TextStyle headerTextStyle = TextStyle( - color: _textStyle.color ?? _chartState._chartTheme.tooltipLabelColor, - fontSize: _textStyle.fontSize, - fontFamily: _textStyle.fontFamily, - fontStyle: _textStyle.fontStyle, - fontWeight: FontWeight.bold, - 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); - headerTextWidth = _measureText(header, headerTextStyle).width; - headerTextHeight = _measureText(header, headerTextStyle).height + 10; + final TextStyle headerTextStyle = + _getTooltiptextStyle(tooltip.textStyle, true, true); + headerSize = _measureText(header, headerTextStyle); + headerTextWidth = headerSize.width; + headerTextHeight = headerSize.height + 10; width = width > headerTextWidth ? width : headerTextWidth; } @@ -803,6 +766,7 @@ class _TooltipPainter extends CustomPainter { void _calculateBackgroundRect( Canvas canvas, double height, double width, double headerTextHeight) { double widthPadding = 15; + Rect boundaryRect; if (_chart is SfCartesianChart && tooltip.canShowMarker != null && tooltip.canShowMarker && @@ -810,6 +774,23 @@ class _TooltipPainter extends CustomPainter { markerSize = 5; widthPadding = 17; } + if (_chart is SfCartesianChart && + (!_chartState._requireAxisTooltip && + (currentTooltipValue != null && + currentTooltipValue.seriesIndex != null))) { + boundaryRect = _chartState._chartAxis._axisClipRect; + } else if ((_chart is SfCartesianChart) && + (_chartState._requireAxisTooltip || + (currentTooltipValue != null && + currentTooltipValue.seriesIndex == null))) { + boundaryRect = _chartState._containerRect; + } else { + if (_chart is SfCartesianChart) { + return; + } else { + boundaryRect = _chartState._chartAreaRect; + } + } Rect rect = Rect.fromLTWH(x, y, width + (2 * markerSize) + widthPadding, height + headerTextHeight + 10); @@ -832,7 +813,7 @@ class _TooltipPainter extends CustomPainter { } if (y > pointerLength + rect.height && y > boundaryRect.top) { - if (_chart is SfCartesianChart) { + if (_chart is SfCartesianChart && !_chartState._requireAxisTooltip) { if (currentSeries._seriesType == 'bubble') { padding = 2; } @@ -843,11 +824,15 @@ class _TooltipPainter extends CustomPainter { nosePointY = rect.top - padding; nosePointX = rect.left; final double tooltipRightEnd = x + (rect.width / 2); - xPos = xPos < boundaryRect.left - ? boundaryRect.left - : tooltipRightEnd > totalWidth - ? totalWidth - rect.width - : xPos; + if (_chart is SfCartesianChart && _chartState._requireAxisTooltip) { + xPos = xPos < boundaryRect.left ? boundaryRect.left : xPos; + } else { + xPos = xPos < boundaryRect.left + ? boundaryRect.left + : tooltipRightEnd > totalWidth + ? totalWidth - rect.width + : xPos; + } yPos = yPos - (pointerLength / 2); } else { isTop = false; @@ -858,11 +843,15 @@ class _TooltipPainter extends CustomPainter { nosePointX = rect.left; nosePointY = (y >= boundaryRect.top ? y : boundaryRect.top) + padding; final double tooltipRightEnd = x + (rect.width / 2); - xPos = xPos < boundaryRect.left - ? boundaryRect.left - : tooltipRightEnd > totalWidth - ? totalWidth - rect.width - : xPos; + if (_chart is SfCartesianChart && _chartState._requireAxisTooltip) { + xPos = xPos < boundaryRect.left ? boundaryRect.left : xPos; + } else { + xPos = xPos < boundaryRect.left + ? boundaryRect.left + : tooltipRightEnd > totalWidth + ? totalWidth - rect.width + : xPos; + } } if (xPos <= boundaryRect.left + 5) { xPos = xPos + 5; @@ -870,6 +859,13 @@ class _TooltipPainter extends CustomPainter { xPos = xPos - 5; } rect = Rect.fromLTWH(xPos, yPos, rect.width, rect.height); + if (boundaryRect.right < rect.right && + _chart is SfCartesianChart && + _chartState._requireAxisTooltip) { + const padding = 5; + rect = Rect.fromLTRB(boundaryRect.right - width - padding, rect.top, + boundaryRect.right + padding, rect.bottom); + } _drawTooltipBackground(canvas, rect, nosePointX, nosePointY, borderRadius, isTop, arrowPath, isLeft, isRight, tooltipAnimation); } @@ -960,6 +956,8 @@ class _TooltipPainter extends CustomPainter { rectF.width * animationFactor, rectF.height * animationFactor); + _tooltipRect = rect; + final RRect tooltipRect = RRect.fromRectAndCorners( rect, bottomLeft: Radius.circular(borderRadius), @@ -969,20 +967,15 @@ class _TooltipPainter extends CustomPainter { ); _drawTooltipPath(canvas, tooltipRect, rect, backgroundPath, isTop, isLeft, isRight, startX, endX, animationFactor, xPos, yPos); - - final TextStyle textStyle = TextStyle( - color: tooltip.textStyle.color?.withOpacity(tooltip.opacity) ?? - _chartState._chartTheme.tooltipLabelColor, - fontSize: tooltip.textStyle.fontSize * animationFactor, - fontFamily: tooltip.textStyle.fontFamily, - fontWeight: tooltip.textStyle.fontWeight, - fontStyle: tooltip.textStyle.fontStyle); + final TextStyle textStyle = _getTooltiptextStyle( + tooltip.textStyle, false, false, animationFactor); final Size result = _measureText(stringValue, textStyle); _drawTooltipText(canvas, tooltipRect, textStyle, result, animationFactor); if (_chart is SfCartesianChart && tooltip.canShowMarker && chartTooltipState._needMarker) { - _drawTooltipMarker(canvas, tooltipRect, textStyle, animationFactor); + _drawTooltipMarker( + canvas, tooltipRect, textStyle, animationFactor, result); } xPos = null; yPos = null; @@ -1010,25 +1003,33 @@ class _TooltipPainter extends CustomPainter { factor = rect.bottom; backgroundPath.moveTo(rect.right - 20, factor); backgroundPath.lineTo(xPos, yPos); - backgroundPath.lineTo(rect.right - 20, rect.top + rect.height / 2); + backgroundPath.lineTo(rect.right, factor - borderRadius); + backgroundPath.arcToPoint(Offset(rect.right - borderRadius, factor), + radius: Radius.circular(borderRadius)); backgroundPath.lineTo(rect.right - 20, factor); } else if (!isTop && isRight) { factor = rect.top; backgroundPath.moveTo(rect.right - 20, factor); backgroundPath.lineTo(xPos, yPos); - backgroundPath.lineTo(rect.right - 20, rect.top + rect.height / 2); + backgroundPath.lineTo(rect.right, factor + borderRadius); + backgroundPath.arcToPoint(Offset(rect.right - borderRadius, factor), + radius: Radius.circular(borderRadius), clockwise: false); backgroundPath.lineTo(rect.right - 20, factor); } else if (isTop && isLeft) { factor = rect.bottom; backgroundPath.moveTo(rect.left + 20, factor); backgroundPath.lineTo(xPos, yPos); - backgroundPath.lineTo(rect.left + 20, rect.top + rect.height / 2); + backgroundPath.lineTo(rect.left, factor - borderRadius); + backgroundPath.arcToPoint(Offset(rect.left + borderRadius, factor), + radius: Radius.circular(borderRadius), clockwise: false); backgroundPath.lineTo(rect.left + 20, factor); } else if (!isTop && isLeft) { factor = rect.top; backgroundPath.moveTo(rect.left + 20, factor); backgroundPath.lineTo(xPos, yPos); - backgroundPath.lineTo(rect.left + 20, rect.top + rect.height / 2); + backgroundPath.lineTo(rect.left, factor + borderRadius); + backgroundPath.arcToPoint(Offset(rect.left + borderRadius, factor), + radius: Radius.circular(borderRadius)); backgroundPath.lineTo(rect.left + 20, factor); } else { if (isTop) { @@ -1079,8 +1080,8 @@ class _TooltipPainter extends CustomPainter { /// draw marker inside the tooltip void _drawTooltipMarker(Canvas canvas, RRect tooltipRect, TextStyle textStyle, - double animationFactor) { - final Size tooltipStringResult = _measureText(stringValue, textStyle); + double animationFactor, Size tooltipMarkerResult) { + final Size tooltipStringResult = tooltipMarkerResult; if (tooltip.shared) { Size result1 = const Size(0, 0); String str = ''; @@ -1117,7 +1118,7 @@ class _TooltipPainter extends CustomPainter { Canvas canvas) { _seriesRenderer._isMarkerRenderEvent = true; final Path markerPath = _getMarkerShapesPath( - markerType, + _seriesRenderer._series.markerSettings.shape, markerPoint, Size((2 * markerSize) * animationFactor, (2 * markerSize) * animationFactor), @@ -1138,7 +1139,7 @@ class _TooltipPainter extends CustomPainter { markerPaint = _getLinearGradientPaint( _seriesRenderer._series.gradient, _getMarkerShapesPath( - markerType, + _seriesRenderer._series.markerSettings.shape, Offset(markerPoint.dx, markerPoint.dy), Size((2 * markerSize) * animationFactor, (2 * markerSize) * animationFactor), @@ -1159,35 +1160,11 @@ class _TooltipPainter extends CustomPainter { Size result, double animationFactor) { const double padding = 10; final num _maxLinesOfTooltipContent = _getMaxLinesContent(stringValue); - //ignore: prefer_final_locals - TextStyle _textStyle = tooltip.textStyle; if (header != null && header.isNotEmpty) { - final TextStyle headerTextStyle = TextStyle( - color: tooltip.textStyle.color?.withOpacity(tooltip.opacity) ?? - _chartState._chartTheme.tooltipLabelColor, - fontSize: tooltip.textStyle.fontSize * animationFactor, - fontFamily: tooltip.textStyle.fontFamily, - fontStyle: tooltip.textStyle.fontStyle, - fontWeight: FontWeight.bold, - 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); + final TextStyle headerTextStyle = + _getTooltiptextStyle(tooltip.textStyle, true, false, animationFactor); final Size headerResult = _measureText(header, headerTextStyle); - + final num _maxLinesOfHeader = _getMaxLinesContent(header); _drawText( tooltip, canvas, @@ -1196,18 +1173,29 @@ class _TooltipPainter extends CustomPainter { (tooltipRect.left + tooltipRect.width / 2) - headerResult.width / 2, tooltipRect.top + padding / 2), - headerTextStyle); + headerTextStyle, + _maxLinesOfHeader); final Paint dividerPaint = Paint(); dividerPaint.color = _chartState._chartTheme.tooltipLabelColor .withOpacity(tooltip.opacity); dividerPaint.strokeWidth = 0.5 * animationFactor; dividerPaint.style = PaintingStyle.stroke; + num lineOffset = 0; + if (tooltip != null && + tooltip.format != null && + tooltip.format.isNotEmpty) { + if (tooltip.textAlignment == ChartAlignment.near) { + lineOffset = padding; + } else if (tooltip.textAlignment == ChartAlignment.far) { + lineOffset = -padding; + } + } if (animationFactor > 0.5) { canvas.drawLine( - Offset(tooltipRect.left + padding, + Offset(tooltipRect.left + padding - lineOffset, tooltipRect.top + headerResult.height + padding), - Offset(tooltipRect.right - padding, + Offset(tooltipRect.right - padding - lineOffset, tooltipRect.top + headerResult.height + padding), dividerPaint); } @@ -1236,20 +1224,59 @@ class _TooltipPainter extends CustomPainter { } } + /// Get the text style of tooltip. + TextStyle _getTooltiptextStyle( + TextStyle textStyle, bool isHeader, bool isMeasureText, + [double animationFactor]) { + /// Default size of tooltip text, if the user is not spcified the font size. + const int textSize = 12; + final TextStyle tooltipTextStyle = TextStyle( + color: textStyle.color?.withOpacity(tooltip.opacity) ?? + _chartState._chartTheme.tooltipLabelColor, + fontSize: isMeasureText + ? textStyle.fontSize + : (textStyle.fontSize ?? textSize) * animationFactor, + fontWeight: isHeader ? FontWeight.bold : textStyle.fontWeight, + fontFamily: textStyle.fontFamily, + fontStyle: textStyle.fontStyle, + inherit: textStyle.inherit, + backgroundColor: textStyle.backgroundColor, + letterSpacing: textStyle.letterSpacing, + wordSpacing: textStyle.wordSpacing, + textBaseline: textStyle.textBaseline, + height: textStyle.height, + locale: textStyle.locale, + foreground: textStyle.foreground, + background: textStyle.background, + shadows: textStyle.shadows, + fontFeatures: textStyle.fontFeatures, + decoration: textStyle.decoration, + decorationColor: textStyle.decorationColor, + decorationStyle: textStyle.decorationStyle, + decorationThickness: textStyle.decorationThickness, + debugLabel: textStyle.debugLabel, + fontFamilyFallback: textStyle.fontFamilyFallback); + return tooltipTextStyle; + } + ///draw tooltip text void _drawText(dynamic tooltip, Canvas canvas, String text, Offset point, TextStyle style, [int maxLines, int rotation]) { TextAlign tooltipTextAlign = TextAlign.start; + num pointX = point.dx; if (tooltip != null && tooltip.format != null && tooltip.format.isNotEmpty) { if (tooltip.textAlignment == ChartAlignment.near) { tooltipTextAlign = TextAlign.start; - } else if (tooltip.textAlignment == ChartAlignment.center) { - tooltipTextAlign = TextAlign.center; + pointX = + _chartState._tooltipBehaviorRenderer._painter._tooltipRect.left; } else if (tooltip.textAlignment == ChartAlignment.far) { tooltipTextAlign = TextAlign.end; + pointX = + _chartState._tooltipBehaviorRenderer._painter._tooltipRect.right - + _measureText(text, style).width; } } @@ -1262,7 +1289,7 @@ class _TooltipPainter extends CustomPainter { maxLines: maxLines ?? 1); tp.layout(); canvas.save(); - canvas.translate(point.dx, point.dy); + canvas.translate(pointX, point.dy); if (rotation != null && rotation > 0) { canvas.rotate(_degreeToRadian(rotation)); } @@ -1285,7 +1312,11 @@ class _TooltipPainter extends CustomPainter { final bool needAnimate = !(_chart is SfCartesianChart) && (tooltipBehaviorRenderer._painter.currentTooltipValue?.pointIndex == tooltipBehaviorRenderer._painter.prevTooltipValue?.pointIndex) && + _chartState._chartSeries.currentSeries.explode && !(tooltip.tooltipPosition == TooltipPosition.pointer); + chartTooltipState.animationController.duration = Duration( + milliseconds: + tooltipBehaviorRenderer._tooltipBehavior.animationDuration); if ((tooltipBehaviorRenderer._painter.prevTooltipValue != null && tooltipBehaviorRenderer._painter.currentTooltipValue == null) || needAnimate) { @@ -1293,7 +1324,8 @@ class _TooltipPainter extends CustomPainter { tooltipBehaviorRenderer._painter.currentTooltipValue = tooltipBehaviorRenderer._painter.prevTooltipValue; } else { - if (tooltip.tooltipPosition == TooltipPosition.pointer) { + if (tooltip.tooltipPosition == TooltipPosition.pointer && + (!(_chart is SfCartesianChart) || currentSeries._isRectSeries)) { chartTooltipState.animationController.forward(from: 0.0); tooltipBehaviorRenderer._painter.currentTooltipValue = tooltipBehaviorRenderer._painter.prevTooltipValue; @@ -1326,6 +1358,9 @@ class _TooltipPainter extends CustomPainter { _chartState._tooltipBehaviorRenderer._painter != null && _chartState ._tooltipBehaviorRenderer._painter._chartState._animateCompleted) { + chartTooltipState.animationController.duration = Duration( + milliseconds: _chartState + ._tooltipBehaviorRenderer._tooltipBehavior.animationDuration); chartTooltipState.animationController.forward(from: 0.0); _chartState._tooltipBehaviorRenderer._painter.canResetPath = false; //render @@ -1396,6 +1431,40 @@ class _TooltipPainter extends CustomPainter { } } + /// To show the axis label tooltip for trimmed axes label texts. + void _showAxisTooltip(Offset position, dynamic chart, String text) { + final _TooltipPainter painter = + _chartState._tooltipBehaviorRenderer._painter; + if (painter != null) { + painter.canResetPath = false; + painter.header = ''; + painter.stringValue = text; + painter._calculateLocation(position); + chartTooltipState._needMarker = false; + if ((painter.prevTooltipValue != null) && + (painter.prevTooltipValue.pointIndex == + painter.currentTooltipValue.pointIndex)) { + chartTooltipState.animationController.duration = + Duration(milliseconds: 0); + chartTooltipState.animationController.forward(from: 0.0); + } else { + chartTooltipState.animationController.duration = Duration( + milliseconds: _chartState + ._tooltipBehaviorRenderer._tooltipBehavior.animationDuration); + chartTooltipState.animationController.forward(from: 0.0); + } + + if (painter.timer != null) { + painter.timer.cancel(); + } + } + painter.timer = Timer(Duration(milliseconds: tooltip.duration.toInt()), () { + chartTooltipState.show = false; + chartTooltipState.tooltipRepaintNotifier.value++; + painter.canResetPath = true; + }); + } + /// To hide the tooltip when the timer ends void hide() { final TooltipBehaviorRenderer tooltipBehaviorRenderer = @@ -1428,9 +1497,24 @@ class _TooltipPainter extends CustomPainter { } if (tooltipBehaviorRenderer._painter.prevTooltipValue != null && tooltipBehaviorRenderer._painter.currentTooltipValue == null) { + chartTooltipState.animationController.duration = Duration( + milliseconds: + tooltipBehaviorRenderer._tooltipBehavior.animationDuration); chartTooltipState.animationController.forward(from: 0.0); tooltipBehaviorRenderer._painter.currentTooltipValue = tooltipBehaviorRenderer._painter.prevTooltipValue; + } else if (tooltipBehaviorRenderer._painter.prevTooltipValue != null && + tooltipBehaviorRenderer._painter.currentTooltipValue != null && + tooltipBehaviorRenderer._tooltipBehavior.tooltipPosition != + TooltipPosition.auto && + (!(seriesRenderer is CartesianSeriesRenderer) || + seriesRenderer._isRectSeries) && + tooltipBehaviorRenderer._painter.currentTooltipValue.seriesIndex == + tooltipBehaviorRenderer._painter.prevTooltipValue.seriesIndex && + tooltipBehaviorRenderer._painter.currentTooltipValue.pointIndex == + tooltipBehaviorRenderer._painter.prevTooltipValue.pointIndex) { + chartTooltipState.animationController.duration = Duration(seconds: 0); + chartTooltipState.animationController.forward(from: 0.0); } if (!mouseTooltip) { hide(); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_template.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_template.dart index 46f5491a4..be4c57c0b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_template.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_template.dart @@ -80,6 +80,7 @@ class _TooltipTemplateState extends State<_TooltipTemplate> Widget build(BuildContext context) { Widget tooltipWidget; isOutOfBoundInTop = false; + num paddingTop; const double arrowHeight = 5.0, arrowWidth = 10.0; if (widget.show) { if (needMeasure) { @@ -97,10 +98,16 @@ class _TooltipTemplateState extends State<_TooltipTemplate> tooltipSize.height); final Offset tooltipLocation = _getTooltipLocation(tooltipRect, widget.clipRect); - + if (widget.rect.top < widget.clipRect.top) { + paddingTop = widget.clipRect.top + arrowHeight; + top = tooltipLocation.dy; + } final Offset arrowLocation = Offset( tooltipLocation.dx, isOutOfBoundInTop ? top : top - arrowHeight); top = isOutOfBoundInTop ? top + arrowHeight : tooltipRect.top; + if (widget.rect.top >= widget.clipRect.top) { + paddingTop = top; + } tooltipWidget = Stack(children: [ CustomPaint( size: Size(arrowHeight, arrowWidth), @@ -114,7 +121,8 @@ class _TooltipTemplateState extends State<_TooltipTemplate> tooltipSize)), Container( child: Padding( - padding: EdgeInsets.fromLTRB(tooltipLocation.dx, top, 0, 0), + padding: + EdgeInsets.fromLTRB(tooltipLocation.dx, paddingTop, 0, 0), child: ScaleTransition( scale: _animation, child: widget.template))) ]); @@ -172,12 +180,27 @@ class _TooltipTemplateState extends State<_TooltipTemplate> if (widget.show && mounted && ((prevTooltipValue == null && currentTooltipValue == null) || + ((widget.chartState is SfCartesianChartState && + widget.chartState._chartSeries + .visibleSeriesRenderers[seriesIndex]._isRectSeries && + widget.tooltipBehavior.tooltipPosition != + TooltipPosition.auto)) || (prevTooltipValue?.seriesIndex != currentTooltipValue?.seriesIndex || + prevTooltipValue?.outlierIndex != + currentTooltipValue?.outlierIndex || prevTooltipValue?.pointIndex != currentTooltipValue?.pointIndex))) { - needMeasure = true; - tooltipSize = const Size(0, 0); + final bool reRender = (widget + .chartState._tooltipBehaviorRenderer._isHovering && + prevTooltipValue != null && + currentTooltipValue != null && + prevTooltipValue.seriesIndex == currentTooltipValue.seriesIndex && + prevTooltipValue.pointIndex == currentTooltipValue.pointIndex && + prevTooltipValue.outlierIndex == currentTooltipValue.outlierIndex); + needMeasure = !reRender; + _controller.duration = Duration(milliseconds: reRender ? 0 : 300); + tooltipSize = reRender ? tooltipSize : const Size(0, 0); setState(() {}); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball.dart index c4a306376..4174231be 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball.dart @@ -20,6 +20,7 @@ class TrackballBehavior { this.lineColor, this.lineWidth = 1, this.shouldAlwaysShow = false, + this.builder, double hideDelay, }) : activationMode = activationMode ?? ActivationMode.longPress, tooltipAlignment = tooltipAlignment ?? ChartAlignment.center, @@ -216,6 +217,86 @@ class 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 + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// trackballBehavior: TrackballBehavior( + /// enable: true, + /// builder: (BuildContext context, TrackballDetails trackballDetails) { + /// return Container( + /// height: _selectDisplayMode == + /// TrackballDisplayMode.floatAllPoints || + /// _selectDisplayMode == + /// TrackballDisplayMode.nearestPoint + /// ? 50 + /// : 75, + /// width: 100, + /// decoration: const BoxDecoration( + /// color: Color.fromRGBO(66, 244, 164, 1)), + /// child: Row( + /// children: [ + /// Container( + /// width: 50, + /// child: Image.asset('images/bike.png')), + /// _selectDisplayMode == + /// TrackballDisplayMode.floatAllPoints || + /// _selectDisplayMode == + /// TrackballDisplayMode.nearestPoint + /// ? Container( + /// width: 50, + /// child: Column( + /// children: [ + /// Container( + /// height: 25, + /// alignment: Alignment.center, + /// child: Text( + /// '${trackballDetails.point.x.toString()}')), + /// Container( + /// height: 25, + /// alignment: Alignment.center, + /// child: Text( + /// '${trackballDetails.point.y.toString()}')) + /// ], + /// )) + /// : Container( + /// width: 50, + /// child: Column( + /// children: [ + /// Container( + /// height: 25, + /// alignment: Alignment.center, + /// child: Text( + /// '${trackballDetails.dataValues[0].toString()}')), + /// Container( + /// height: 25, + /// alignment: Alignment.center, + /// child: Text( + /// '${trackballDetails.dataValues[1].toString()}')), + /// Container( + /// height: 25, + /// alignment: Alignment.center, + /// child: Text( + /// '${trackballDetails.dataValues[3].toString()}')) + /// ], + /// )) + /// ], + /// )); + /// }), + /// )); + ///} + ///``` + final ChartTrackballBuilder builder; + SfCartesianChartState _chartState; ///Options to customize the markers that are displayed when trackball is enabled. @@ -243,18 +324,21 @@ class TrackballBehavior { final TrackballBehaviorRenderer trackballBehaviorRenderer = chartState._trackballBehaviorRenderer; final List visibleSeriesRenderer = - trackballBehaviorRenderer - ._trackballPainter.chartState._chartSeries.visibleSeriesRenderers; + chartState._chartSeries.visibleSeriesRenderers; final CartesianSeriesRenderer seriesRenderer = visibleSeriesRenderer[0]; - if (trackballBehaviorRenderer._trackballPainter != null && + if ((trackballBehaviorRenderer._trackballPainter != null || + builder != null) && activationMode != ActivationMode.none) { + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; if (coordinateUnit != 'pixel') { - trackballBehaviorRenderer - ._trackballPainter.chartState._trackballWithoutTouch = false; final _ChartLocation location = _calculatePoint( - x is DateTime ? x.millisecondsSinceEpoch : x, + x is DateTime + ? x.millisecondsSinceEpoch + : (x is String && xAxisRenderer is CategoryAxisRenderer) + ? xAxisRenderer._labels.indexOf(x) + : x, y, - seriesRenderer._xAxisRenderer, + xAxisRenderer, seriesRenderer._yAxisRenderer, seriesRenderer._chartState._requireInvertedAxis, seriesRenderer._series, @@ -262,15 +346,18 @@ class TrackballBehavior { x = location.x; y = location.y; } - trackballBehaviorRenderer._trackballPainter - ._generateAllPoints(Offset(x.toDouble(), y)); + if (trackballBehaviorRenderer._trackballPainter != null) { + trackballBehaviorRenderer._isTrackballTemplate = false; + trackballBehaviorRenderer._generateAllPoints(Offset(x.toDouble(), y)); + } else if (builder != null && (!trackballBehaviorRenderer._isMoving)) { + trackballBehaviorRenderer + ._showTemplateTrackball(Offset(x.toDouble(), y)); + } } - trackballBehaviorRenderer._trackballPainter.canResetPath = false; - if (trackballBehaviorRenderer - ._trackballPainter.chartState._trackballWithoutTouch) { - trackballBehaviorRenderer - ._trackballPainter.chartState._trackballRepaintNotifier.value++; + if (trackballBehaviorRenderer._trackballPainter != null) { + trackballBehaviorRenderer._trackballPainter.canResetPath = false; + chartState._trackballRepaintNotifier.value++; } } @@ -281,16 +368,20 @@ class TrackballBehavior { final SfCartesianChartState chartState = _chartState; final TrackballBehaviorRenderer trackballBehaviorRenderer = chartState._trackballBehaviorRenderer; - if (trackballBehaviorRenderer._trackballPainter != null && + if ((trackballBehaviorRenderer._trackballPainter != null || + builder != null) && activationMode != ActivationMode.none) { - if (_validIndex( - pointIndex, 0, trackballBehaviorRenderer._trackballPainter.chart)) { - trackballBehaviorRenderer._trackballPainter.showTrackball( - trackballBehaviorRenderer._trackballPainter.chartState._chartSeries - .visibleSeriesRenderers, - pointIndex); + if (_validIndex(pointIndex, 0, chartState._chart)) { + trackballBehaviorRenderer._showTrackball( + chartState._chartSeries.visibleSeriesRenderers, + pointIndex, + trackballBehaviorRenderer); + } + if (trackballBehaviorRenderer._trackballPainter != null) { + trackballBehaviorRenderer._trackballPainter.canResetPath = false; + trackballBehaviorRenderer + ._trackballPainter.chartState._trackballRepaintNotifier.value++; } - trackballBehaviorRenderer._trackballPainter.canResetPath = false; } } @@ -300,25 +391,43 @@ class TrackballBehavior { final TrackballBehaviorRenderer trackballBehaviorRenderer = chartState._trackballBehaviorRenderer; if (trackballBehaviorRenderer._trackballPainter != null && + !trackballBehaviorRenderer._isTrackballTemplate && activationMode != ActivationMode.none) { - trackballBehaviorRenderer._trackballPainter.canResetPath = false; - ValueNotifier(trackballBehaviorRenderer - ._trackballPainter.chartState._trackballRepaintNotifier.value++); - if (trackballBehaviorRenderer._trackballPainter.timer != null) { - trackballBehaviorRenderer._trackballPainter.timer.cancel(); + if (chartState._chart.trackballBehavior.activationMode == + ActivationMode.doubleTap) { + trackballBehaviorRenderer._trackballPainter.canResetPath = false; + ValueNotifier(trackballBehaviorRenderer + ._trackballPainter.chartState._trackballRepaintNotifier.value++); + if (trackballBehaviorRenderer._trackballPainter.timer != null) { + trackballBehaviorRenderer._trackballPainter.timer.cancel(); + } } - final double duration = shouldAlwaysShow || - (hideDelay == 0 && - trackballBehaviorRenderer - ._trackballPainter.chartState._enableDoubleTap) - ? 200 - : hideDelay; - trackballBehaviorRenderer._trackballPainter.timer = - Timer(Duration(milliseconds: duration.toInt()), () { + if (!_chartState._isTouchUp) { trackballBehaviorRenderer ._trackballPainter.chartState._trackballRepaintNotifier.value++; trackballBehaviorRenderer._trackballPainter.canResetPath = true; - }); + } else { + final double duration = + (hideDelay == 0 && chartState._enableDoubleTap) ? 200 : hideDelay; + if (!shouldAlwaysShow) { + trackballBehaviorRenderer._trackballPainter.timer = + Timer(Duration(milliseconds: duration.toInt()), () { + trackballBehaviorRenderer + ._trackballPainter.chartState._trackballRepaintNotifier.value++; + trackballBehaviorRenderer._trackballPainter.canResetPath = true; + }); + } + } + } else if (trackballBehaviorRenderer._trackballTemplate != null) { + final GlobalKey key = trackballBehaviorRenderer._trackballTemplate.key; + final _TrackballTemplateState trackballTemplateState = key.currentState; + final double duration = + shouldAlwaysShow || (hideDelay == 0 && chartState._enableDoubleTap) + ? 200 + : hideDelay; + trackballTemplateState?._trackballTimer = Timer( + Duration(milliseconds: duration.toInt()), + trackballTemplateState.hideTrackballTemplate); } } } @@ -327,21 +436,1205 @@ class TrackballBehavior { class TrackballBehaviorRenderer with ChartBehavior { /// Creates an argument constructor for Trackball renderer class TrackballBehaviorRenderer(this._chartState); - SfCartesianChart get _chart => _chartState._chart; - final SfCartesianChartState _chartState; - TrackballBehavior get _trackballBehavior => _chart.trackballBehavior; /// Check whether long press activated or not . bool _isLongPressActivated; + /// check whether onPointerMove or not. + /// ignore: prefer_final_fields + bool _isMoving = false; + /// Touch position + /// ignore: unused_field Offset _position; /// Holds the instance of trackballPainter. _TrackballPainter _trackballPainter; + _TrackballTemplate _trackballTemplate; + List _visibleLocation = []; + final List _markerShapes = []; + Rect _axisClipRect; + + //ignore: unused_field + double _xPos; + //ignore: unused_field + double _yPos; + List _points = []; + List _currentPointIndices = []; + List _visibleSeriesIndices = []; + List _visibleSeriesList = []; + TrackballGroupingModeInfo _groupingModeInfo; + List<_ChartPointInfo> _chartPointInfo = <_ChartPointInfo>[]; + List _tooltipTop = []; + List _tooltipBottom = []; + final List _xAxesInfo = []; + final List _yAxesInfo = []; + List<_ClosestPoints> _visiblePoints = <_ClosestPoints>[]; + _TooltipPositions _tooltipPosition; + num _tooltipPadding; + bool _isRangeSeries; + bool _isBoxSeries; + 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] + .seriesRenderer + ._series + .markerSettings + .isVisible) + : _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].seriesRenderer._seriesType; + _chartPointInfo[index].seriesRenderer._isMarkerRenderEvent = true; + _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].seriesRenderer, + null, + _trackballBehavior)); + } + } + + /// To show track ball based on point index + void _showTrackball(List visibleSeriesRenderers, + int pointIndex, TrackballBehaviorRenderer trackballBehaviorRenderer) { + _ChartLocation position; + final CartesianSeriesRenderer seriesRenderer = visibleSeriesRenderers[0]; + final Rect rect = seriesRenderer._chartState._chartAxis._axisClipRect; + final List> dataPoints = + >[]; + for (int i = 0; i < seriesRenderer._dataPoints.length; i++) { + if (seriesRenderer._dataPoints[i].isGap != true) { + dataPoints.add(seriesRenderer._dataPoints[i]); + } + } + assert(pointIndex != null, 'Point index must not be null'); + + if (pointIndex != null && + pointIndex.abs() < seriesRenderer._dataPoints.length) { + final int index = pointIndex; + final num xValue = seriesRenderer._dataPoints[index].xValue; + final num yValue = + seriesRenderer._series is _FinancialSeriesBase || + seriesRenderer._seriesType.contains('range') + ? seriesRenderer._dataPoints[index].high + : seriesRenderer._dataPoints[index].yValue; + position = _calculatePoint( + xValue, + yValue, + seriesRenderer._xAxisRenderer, + seriesRenderer._yAxisRenderer, + seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._series, + rect); + if (trackballBehaviorRenderer._trackballPainter != null) { + seriesRenderer._chartState._trackballBehaviorRenderer + ._generateAllPoints(Offset(position.x, position.y)); + } else if (_trackballBehavior.builder != null) { + trackballBehaviorRenderer + ._showTemplateTrackball(Offset(position.x, position.y)); + } + } + } + + void _showTemplateTrackball(Offset position) { + final GlobalKey key = _trackballTemplate.key; + final _TrackballTemplateState trackballTemplateState = key.currentState; + 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] + .seriesRenderer + ._dataPoints[_chartPointInfo[index].dataPointIndex]; + if (_trackballBehavior.tooltipDisplayMode == + TrackballDisplayMode.groupAllPoints) { + _points.add(dataPoint); + _currentPointIndices.add(_chartPointInfo[index].dataPointIndex); + _visibleSeriesIndices.add(_chartState + ._chartSeries.visibleSeriesRenderers + .indexOf(_chartPointInfo[index].seriesRenderer)); + _visibleSeriesList.add(_chartPointInfo[index].seriesRenderer._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 = _chartState._chartAxis._axisClipRect; + _tooltipPadding = _chartState._requireInvertedAxis ? 8 : 5; + _chartPointInfo = <_ChartPointInfo>[]; + _visiblePoints = <_ClosestPoints>[]; + _markerShapes?.clear(); + _tooltipTop = []; + _tooltipBottom = []; + _trackballPainter?._tooltipTop = []; + _trackballPainter?._tooltipBottom = []; + _visibleLocation = []; + final Rect seriesBounds = _axisClipRect; + if (position.dx >= seriesBounds.left && + position.dx <= seriesBounds.right && + position.dy >= seriesBounds.top && + position.dy <= seriesBounds.bottom) { + double xPos = 0, + yPos = 0, + leastX = 0, + openXPos, + openYPos, + closeXPos, + closeYPos, + highXPos, + cummulativePos, + lowerXPos, + lowerYPos, + upperXPos, + upperYPos, + lowYPos, + highYPos, + minYPos, + maxYPos, + maxXPos; + int seriesIndex = 0, index; + final List seriesAxisRenderers = []; + CartesianSeriesRenderer visibleSeriesRenderer, cartesianSeriesRenderer; + ChartAxisRenderer chartAxisRenderer, xAxisRenderer, yAxisRenderer; + CartesianChartPoint chartDataPoint; + _ChartAxis chartAxis; + String seriesType, labelValue, seriesName; + bool invertedAxis; + CartesianSeries series; + num xValue, + yValue, + minimumValue, + maximumValue, + lowerQuartileValue, + upperQuartileValue, + meanValue, + highValue, + lowValue, + openValue, + closeValue, + bubbleSize, + cumulativeValue; + Rect axisClipRect; + final TrackballDisplayMode tooltipDisplayMode = + _chartState._chart.trackballBehavior.tooltipDisplayMode; + _ChartLocation highLocation, maxLocation; + for (final CartesianSeriesRenderer seriesRenderer + in _chartState._seriesRenderers) { + visibleSeriesRenderer = seriesRenderer; + chartAxisRenderer = visibleSeriesRenderer._xAxisRenderer; + if (chartAxisRenderer == null) { + continue; + } + if (!seriesAxisRenderers.contains(chartAxisRenderer)) { + seriesAxisRenderers.add(chartAxisRenderer); + for (final CartesianSeriesRenderer axisSeriesRenderer + in chartAxisRenderer._seriesRenderers) { + cartesianSeriesRenderer = axisSeriesRenderer; + seriesType = cartesianSeriesRenderer._seriesType; + _isRangeSeries = seriesType.contains('range') || + seriesType.contains('hilo') || + seriesType == 'candle'; + _isBoxSeries = seriesType == 'boxandwhisker'; + if (axisSeriesRenderer._visible == false || + (axisSeriesRenderer._dataPoints.isEmpty && + !axisSeriesRenderer._isRectSeries)) { + continue; + } + if (cartesianSeriesRenderer._dataPoints.isNotEmpty) { + final List> nearestDataPoints = + _getNearestChartPoints( + position.dx, + position.dy, + chartAxisRenderer, + visibleSeriesRenderer._yAxisRenderer, + cartesianSeriesRenderer); + if (nearestDataPoints == null) { + continue; + } + for (final CartesianChartPoint dataPoint + in nearestDataPoints) { + index = axisSeriesRenderer._dataPoints.indexOf(dataPoint); + chartDataPoint = cartesianSeriesRenderer._dataPoints[index]; + xAxisRenderer = cartesianSeriesRenderer._xAxisRenderer; + yAxisRenderer = cartesianSeriesRenderer._yAxisRenderer; + chartAxis = cartesianSeriesRenderer._chartState._chartAxis; + invertedAxis = _chartState._requireInvertedAxis; + series = cartesianSeriesRenderer._series; + xValue = chartDataPoint.xValue; + if (seriesType != 'boxandwhisker') { + yValue = chartDataPoint.yValue; + } + minimumValue = chartDataPoint.minimum; + maximumValue = chartDataPoint.maximum; + lowerQuartileValue = chartDataPoint.lowerQuartile; + upperQuartileValue = chartDataPoint.upperQuartile; + meanValue = chartDataPoint.mean; + highValue = chartDataPoint.high; + lowValue = chartDataPoint.low; + openValue = chartDataPoint.open; + closeValue = chartDataPoint.close; + seriesName = cartesianSeriesRenderer._series.name ?? + 'Series $seriesIndex'; + bubbleSize = chartDataPoint.bubbleSize; + cumulativeValue = chartDataPoint.cumulativeValue; + axisClipRect = _calculatePlotOffset( + chartAxis._axisClipRect, + Offset(xAxisRenderer._axis.plotOffset, + yAxisRenderer._axis.plotOffset)); + cummulativePos = _calculatePoint( + xValue, + cumulativeValue, + xAxisRenderer, + yAxisRenderer, + invertedAxis, + series, + axisClipRect) + .y; + xPos = _calculatePoint( + xValue, + seriesType.contains('stacked') + ? cumulativeValue + : yValue, + xAxisRenderer, + yAxisRenderer, + invertedAxis, + series, + axisClipRect) + .x; + if (!xPos.toDouble().isNaN) { + if (seriesIndex == 0 || + ((leastX - position.dx) > (leastX - xPos))) { + leastX = xPos; + } + labelValue = _getTrackballLabelText( + cartesianSeriesRenderer, + xValue, + yValue, + lowValue, + highValue, + openValue, + closeValue, + minimumValue, + maximumValue, + lowerQuartileValue, + upperQuartileValue, + meanValue, + seriesName, + bubbleSize, + cumulativeValue, + dataPoint); + yPos = seriesType.contains('stacked') + ? cummulativePos + : _calculatePoint(xValue, yValue, xAxisRenderer, + yAxisRenderer, invertedAxis, series, axisClipRect) + .y; + if (_isRangeSeries) { + lowYPos = _calculatePoint(xValue, lowValue, xAxisRenderer, + yAxisRenderer, invertedAxis, series, axisClipRect) + .y; + highLocation = _calculatePoint( + xValue, + highValue, + xAxisRenderer, + yAxisRenderer, + invertedAxis, + series, + axisClipRect); + highYPos = highLocation.y; + highXPos = highLocation.x; + if (seriesType == 'hiloopenclose' || + seriesType == 'candle') { + openXPos = dataPoint.openPoint.x; + openYPos = dataPoint.openPoint.y; + closeXPos = dataPoint.closePoint.x; + closeYPos = dataPoint.closePoint.y; + } + } else if (seriesType == 'boxandwhisker') { + minYPos = _calculatePoint( + xValue, + minimumValue, + xAxisRenderer, + yAxisRenderer, + invertedAxis, + series, + axisClipRect) + .y; + maxLocation = _calculatePoint( + xValue, + maximumValue, + xAxisRenderer, + yAxisRenderer, + invertedAxis, + series, + axisClipRect); + maxXPos = maxLocation.x; + maxYPos = maxLocation.y; + lowerXPos = dataPoint.lowerQuartilePoint.x; + lowerYPos = dataPoint.lowerQuartilePoint.y; + upperXPos = dataPoint.upperQuartilePoint.x; + upperYPos = dataPoint.upperQuartilePoint.y; + } + final Rect rect = seriesBounds.intersect(Rect.fromLTWH( + xPos - 1, + _isRangeSeries + ? highYPos - 1 + : _isBoxSeries + ? maxYPos - 1 + : yPos - 1, + 2, + 2)); + if (seriesBounds.contains(Offset( + xPos, + _isRangeSeries + ? highYPos + : _isBoxSeries + ? maxYPos + : yPos)) || + seriesBounds.overlaps(rect)) { + _visiblePoints.add(_ClosestPoints( + closestPointX: !_isRangeSeries + ? xPos + : _isBoxSeries + ? maxXPos + : highXPos, + closestPointY: _isRangeSeries + ? highYPos + : _isBoxSeries + ? maxYPos + : yPos)); + _addChartPointInfo( + cartesianSeriesRenderer, + xPos, + yPos, + index, + !_isTrackballTemplate ? labelValue : null, + seriesIndex, + lowYPos, + highXPos, + highYPos, + openXPos, + openYPos, + closeXPos, + closeYPos, + minYPos, + maxXPos, + maxYPos, + lowerXPos, + lowerYPos, + upperXPos, + upperYPos); + if (tooltipDisplayMode == + TrackballDisplayMode.groupAllPoints && + leastX >= seriesBounds.left) { + invertedAxis ? yPos = leastX : xPos = leastX; + } + } + } + } + seriesIndex++; + } + _validateNearestXValue( + leastX, cartesianSeriesRenderer, position.dx, position.dy); + } + 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)) + : _chartPointInfo.sort( + (_ChartPointInfo a, _ChartPointInfo b) => + a.yPosition.compareTo(b.yPosition)); + } + if (tooltipDisplayMode == TrackballDisplayMode.nearestPoint || + (seriesRenderer._isRectSeries && + tooltipDisplayMode != + TrackballDisplayMode.groupAllPoints)) { + _validateNearestPointForAllSeries( + leastX, _chartPointInfo, position.dx, position.dy); + } + } + } + } + _triggerTrackballRenderCallback(); + } + _chartPointInfo = _getValidPoints(_chartPointInfo, position); + } + + /// To get valid points of trackball + List<_ChartPointInfo> _getValidPoints( + List<_ChartPointInfo> points, Offset position) { + final List<_ChartPointInfo> validPoints = <_ChartPointInfo>[]; + for (final _ChartPointInfo point in points) { + if (validPoints.isEmpty) { + validPoints.add(point); + } else if (validPoints[0].seriesRenderer._xAxisRenderer == + point.seriesRenderer._xAxisRenderer) { + if (!point.seriesRenderer._chartState._requireInvertedAxis) { + if ((validPoints[0].xPosition - position.dx).abs() == + (point.xPosition - position.dx).abs()) { + validPoints.add(point); + } + } else if ((validPoints[0].yPosition - position.dy).abs() == + (point.yPosition - position.dy).abs()) { + validPoints.add(point); + } + } else if ((validPoints[0].xPosition - position.dx).abs() > + (point.xPosition - position.dx).abs()) { + validPoints.clear(); + validPoints.add(point); + } else if ((validPoints[0].xPosition - position.dx).abs() == + (point.xPosition - position.dx).abs()) { + if ((validPoints[0].yPosition - position.dy).abs() > + (point.yPosition - position.dy).abs()) { + validPoints.clear(); + validPoints.add(point); + } + } + } + return validPoints; + } + + /// Event for trackball render + void _triggerTrackballRenderCallback() { + if (_chart.onTrackballPositionChanging != null) { + _chartState._chartPointInfo = + _chartState._trackballBehaviorRenderer._chartPointInfo; + int index; + for (index = 0; index < _chartState._chartPointInfo.length; index++) { + TrackballArgs chartPoint; + chartPoint = TrackballArgs(); + chartPoint.chartPointInfo = _chartState._chartPointInfo[index]; + _chart.onTrackballPositionChanging(chartPoint); + _chartState._chartPointInfo[index].label = + chartPoint.chartPointInfo.label; + _chartState._chartPointInfo[index].header = + chartPoint.chartPointInfo.header; + if (_chartState._chartPointInfo[index].label == null || + _chartState._chartPointInfo[index].label == '') { + _chartState._chartPointInfo.removeAt(index); + _visiblePoints.removeAt(index); + } + } + } + } + + /// To validate the nearest point in all series for trackball + void _validateNearestPointForAllSeries(double leastX, + List<_ChartPointInfo> trackballInfo, double touchXPos, double touchYPos) { + double xPos = 0, yPos; + final List<_ChartPointInfo> tempTrackballInfo = + List<_ChartPointInfo>.from(trackballInfo); + _ChartPointInfo pointInfo, nextPointInfo, previousPointInfo; + num yValue, xValue; + Rect axisClipRect; + int pointInfoIndex; + CartesianChartPoint dataPoint; + ChartAxisRenderer xAxisRenderer, yAxisRenderer; + int i; + for (i = 0; i < tempTrackballInfo.length; i++) { + pointInfo = tempTrackballInfo[i]; + dataPoint = + pointInfo.seriesRenderer._dataPoints[pointInfo.dataPointIndex]; + xAxisRenderer = pointInfo.seriesRenderer._xAxisRenderer; + yAxisRenderer = pointInfo.seriesRenderer._yAxisRenderer; + xValue = dataPoint.xValue; + if (pointInfo.seriesRenderer._seriesType != 'boxandwhisker') { + yValue = dataPoint.yValue; + } + axisClipRect = _calculatePlotOffset( + pointInfo.seriesRenderer._chartState._chartAxis._axisClipRect, + Offset( + xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + xPos = _calculatePoint( + xValue, + yValue, + xAxisRenderer, + yAxisRenderer, + _chartState._requireInvertedAxis, + pointInfo.seriesRenderer._series, + axisClipRect) + .x; + trackballInfo = _getRectSeriesPointInfo(trackballInfo, pointInfo, xValue, + yValue, touchXPos, leastX, axisClipRect); + if (_chartState._chart.trackballBehavior.tooltipDisplayMode != + TrackballDisplayMode.floatAllPoints && + (!pointInfo.seriesRenderer._chartState._requireInvertedAxis)) { + if (leastX != xPos) { + trackballInfo.remove(pointInfo); + } + pointInfoIndex = tempTrackballInfo.indexOf(pointInfo); + yPos = touchYPos; + if (pointInfoIndex < tempTrackballInfo.length - 1) { + nextPointInfo = tempTrackballInfo[pointInfoIndex + 1]; + if ((pointInfo.yPosition > yPos && pointInfoIndex == 0)) { + continue; + } + if (!(yPos < + (nextPointInfo.yPosition - + ((nextPointInfo.yPosition - pointInfo.yPosition) / 2)))) { + trackballInfo.remove(pointInfo); + } else if (pointInfoIndex != 0) { + previousPointInfo = tempTrackballInfo[pointInfoIndex - 1]; + if (yPos < + (pointInfo.yPosition - + ((pointInfo.yPosition - previousPointInfo.yPosition) / + 2))) { + trackballInfo.remove(pointInfo); + } + } + } else { + if (pointInfoIndex != 0 && + pointInfoIndex == tempTrackballInfo.length - 1) { + previousPointInfo = tempTrackballInfo[pointInfoIndex - 1]; + if (yPos < previousPointInfo.yPosition) { + trackballInfo.remove(pointInfo); + } + if (yPos < + (pointInfo.yPosition - + ((pointInfo.yPosition - previousPointInfo.yPosition) / + 2))) { + trackballInfo.remove(pointInfo); + } + } + } + } + } + } + + /// It returns the trackball information for all series + List<_ChartPointInfo> _getRectSeriesPointInfo( + List<_ChartPointInfo> trackballInfo, + _ChartPointInfo pointInfo, + num xValue, + num yValue, + double touchXPos, + double leastX, + Rect axisClipRect) { + if (pointInfo.seriesRenderer._isRectSeries && + _chartState._chart.enableSideBySideSeriesPlacement && + _chartState._chart.trackballBehavior.tooltipDisplayMode != + TrackballDisplayMode.groupAllPoints) { + final ChartAxisRenderer xAxisRenderer = + pointInfo.seriesRenderer._xAxisRenderer, + yAxisRenderer = pointInfo.seriesRenderer._yAxisRenderer; + final bool isXAxisInverse = _chartState._requireInvertedAxis; + final CartesianSeries series = + pointInfo.seriesRenderer._series; + final _VisibleRange sideBySideInfo = + _calculateSideBySideInfo(pointInfo.seriesRenderer, _chartState); + final double xStartValue = xValue + sideBySideInfo.minimum; + final double xEndValue = xValue + sideBySideInfo.maximum; + double xStart = _calculatePoint(xStartValue, yValue, xAxisRenderer, + yAxisRenderer, isXAxisInverse, series, axisClipRect) + .x; + double xEnd = _calculatePoint(xEndValue, yValue, xAxisRenderer, + yAxisRenderer, isXAxisInverse, series, axisClipRect) + .x; + bool isStartIndex = pointInfo.seriesRenderer._sideBySideIndex == 0; + bool isEndIndex = pointInfo.seriesRenderer._sideBySideIndex == + pointInfo.seriesRenderer._chartState._chartSeries + .visibleSeriesRenderers.length - + 1; + final double xPos = touchXPos; + if (isXAxisInverse) { + final double temp = xStart; + xStart = xEnd; + xEnd = temp; + final bool isTemp = isEndIndex; + isEndIndex = isStartIndex; + isStartIndex = isTemp; + } else if (pointInfo.seriesRenderer._chartState._zoomedState == true || + _chartState._chart.trackballBehavior.tooltipDisplayMode != + TrackballDisplayMode.floatAllPoints) { + if (xPos < leastX && isStartIndex) { + if (!(xPos < xStart) && !(xPos < xEnd && xPos >= xStart)) { + trackballInfo.remove(pointInfo); + } + } else if (xPos > leastX && isEndIndex) { + if (!(xPos > xEnd && xPos > xStart) && + !(xPos < xEnd && xPos >= xStart)) { + trackballInfo.remove(pointInfo); + } + } else if (!(xPos < xEnd && xPos >= xStart)) { + trackballInfo.remove(pointInfo); + } + } + } + return trackballInfo; + } + + /// To find the nearest x value to render a trackball + void _validateNearestXValue( + double leastX, + CartesianSeriesRenderer seriesRenderer, + double touchXPos, + double touchYPos) { + final List<_ChartPointInfo> leastPointInfo = <_ChartPointInfo>[]; + final Rect axisClipRect = _calculatePlotOffset( + seriesRenderer._chartState._chartAxis._axisClipRect, + Offset(seriesRenderer._xAxisRenderer._axis.plotOffset, + seriesRenderer._yAxisRenderer._axis.plotOffset)); + final bool invertedAxis = seriesRenderer._chartState._requireInvertedAxis; + double nearPointX = invertedAxis ? axisClipRect.top : axisClipRect.left; + final double touchXValue = invertedAxis ? touchYPos : touchXPos; + double delta = 0, currX; + num xValue, yValue; + CartesianChartPoint dataPoint; + CartesianSeries series; + ChartAxisRenderer xAxisRenderer, yAxisRenderer; + _ChartLocation currXLocation; + for (final _ChartPointInfo pointInfo in _chartPointInfo) { + if (pointInfo.dataPointIndex < seriesRenderer._dataPoints.length) { + dataPoint = seriesRenderer._dataPoints[pointInfo.dataPointIndex]; + xAxisRenderer = pointInfo.seriesRenderer._xAxisRenderer; + yAxisRenderer = pointInfo.seriesRenderer._yAxisRenderer; + xValue = dataPoint.xValue; + if (seriesRenderer._seriesType != 'boxandwhisker') { + yValue = dataPoint.yValue; + } + series = pointInfo.seriesRenderer._series; + currXLocation = _calculatePoint(xValue, yValue, xAxisRenderer, + yAxisRenderer, 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 < + seriesRenderer._dataPoints.length) { + leastX = _getLeastX(_chartPointInfo[0], seriesRenderer, axisClipRect); + } + } + + if (pointInfo.seriesRenderer._seriesType.contains('bar') + ? invertedAxis + : invertedAxis) { + _yPos = leastX; + } else { + _xPos = leastX; + } + } + } + + /// To get the lowest X value to render trackball + double _getLeastX(_ChartPointInfo pointInfo, + CartesianSeriesRenderer seriesRenderer, Rect axisClipRect) { + return _calculatePoint( + seriesRenderer._dataPoints[pointInfo.dataPointIndex].xValue, + 0, + seriesRenderer._xAxisRenderer, + seriesRenderer._yAxisRenderer, + _chartState._requireInvertedAxis, + seriesRenderer._series, + axisClipRect) + .x; + } + + // To render the trackball marker + void _renderTrackballMarker(CartesianSeriesRenderer seriesRenderer, + Canvas canvas, TrackballBehavior trackballBehavior, int index) { + final CartesianChartPoint point = + seriesRenderer._dataPoints[index]; + final TrackballMarkerSettings markerSettings = + trackballBehavior.markerSettings; + if (markerSettings.shape == DataMarkerType.image) { + _drawImageMarker(null, canvas, _chartPointInfo[index].markerXPos, + _chartPointInfo[index].markerYPos, markerSettings, _chartState); + } + final Paint strokePaint = Paint() + ..color = trackballBehavior.markerSettings.borderWidth == 0 + ? Colors.transparent + : ((point.pointColorMapper != null) + ? point.pointColorMapper + : markerSettings.borderColor ?? seriesRenderer._seriesColor) + ..strokeWidth = markerSettings.borderWidth + ..style = PaintingStyle.stroke; + + final Paint fillPaint = Paint() + ..color = markerSettings.color ?? + (_chartState._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(CartesianSeriesRenderer seriesRenderer, double xPos, + double yPos, int dataPointIndex, String label, num seriesIndex, + [double lowYPos, + double highXPos, + double highYPos, + double openXPos, + double openYPos, + double closeXPos, + double closeYPos, + double minYPos, + double maxXPos, + double maxYPos, + double lowerXPos, + double lowerYPos, + double upperXPos, + double upperYPos]) { + final _ChartPointInfo pointInfo = _ChartPointInfo(); + + pointInfo.seriesRenderer = seriesRenderer; + pointInfo.series = seriesRenderer._series; + pointInfo.markerXPos = xPos; + pointInfo.markerYPos = yPos; + pointInfo.xPosition = xPos; + pointInfo.yPosition = yPos; + pointInfo.seriesIndex = seriesIndex; + + if (seriesRenderer._seriesType.contains('hilo') || + seriesRenderer._seriesType.contains('range') || + seriesRenderer._seriesType == 'candle') { + pointInfo.lowYPosition = lowYPos; + pointInfo.highXPosition = highXPos; + pointInfo.highYPosition = highYPos; + if (seriesRenderer._seriesType == 'hiloopenclose' || + seriesRenderer._seriesType == 'candle') { + pointInfo.openXPosition = openXPos; + pointInfo.openYPosition = openYPos; + pointInfo.closeXPosition = closeXPos; + pointInfo.closeYPosition = closeYPos; + } + } else if (seriesRenderer._seriesType.contains('boxandwhisker')) { + pointInfo.minYPosition = minYPos; + pointInfo.maxYPosition = maxYPos; + pointInfo.maxXPosition = maxXPos; + pointInfo.lowerXPosition = lowerXPos; + pointInfo.lowerYPosition = lowerYPos; + pointInfo.upperXPosition = upperXPos; + pointInfo.upperYPosition = upperYPos; + } + + if (seriesRenderer._segments.length > dataPointIndex) { + pointInfo.color = seriesRenderer._segments[dataPointIndex]._color; + } else if (seriesRenderer._segments.length > 1) { + pointInfo.color = + seriesRenderer._segments[seriesRenderer._segments.length - 1]._color; + } + pointInfo.chartDataPoint = seriesRenderer._dataPoints[dataPointIndex]; + pointInfo.dataPointIndex = dataPointIndex; + if (!_isTrackballTemplate) { + pointInfo.label = label; + pointInfo.header = _getHeaderText( + seriesRenderer._dataPoints[dataPointIndex], seriesRenderer); + } + _chartPointInfo.add(pointInfo); + } + + // Method to place the collided tooltips properly + _TooltipPositions _smartTooltipPositions( + List tooltipTop, + List tooltipBottom, + List _xAxesInfo, + List _yAxesInfo, + List<_ChartPointInfo> chartPointInfo, + bool requireInvertedAxis, + [bool isPainterTooltip]) { + _tooltipPadding = _chartState._requireInvertedAxis ? 8 : 5; + num tooltipWidth = 0; + _TooltipPositions tooltipPosition; + for (int i = 0; i < chartPointInfo.length; i++) { + if (requireInvertedAxis) { + _visibleLocation.add(chartPointInfo[i].xPosition); + } else { + _visibleLocation.add((chartPointInfo[i] + .seriesRenderer + ._seriesType + .contains('range') || + chartPointInfo[i].seriesRenderer._seriesType.contains('hilo') || + chartPointInfo[i].seriesRenderer._seriesType == 'candle') + ? chartPointInfo[i].highYPosition + : chartPointInfo[i].seriesRenderer._seriesType == 'boxandwhisker' + ? chartPointInfo[i].maxYPosition + : chartPointInfo[i].yPosition); + } + + 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 chartHeight, startPos; + final bool isTransposed = _chartState._requireInvertedAxis; + dynamic secWidth, width; + final num length = tooltipPosition.tooltipTop.length; + ChartAxisRenderer yAxisRenderer; + final num axesLength = + _chartState._chartAxis._axisRenderersCollection.length; + for (int i = length - 1; i >= 0; i--) { + yAxisRenderer = _yAxesInfo[i]; + for (int k = 0; k < axesLength; k++) { + if (yAxisRenderer == + _chartState._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 == + _chartState._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, + startPoint = 0, + halfHeight, + midPos, + tempTooltipHeight, + temp1TooltipHeight; + int 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( + CartesianSeriesRenderer cartesianSeriesRenderer, + 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; + if (_trackballBehavior.tooltipSettings.format != null) { + dynamic x; + final ChartAxisRenderer axisRenderer = + cartesianSeriesRenderer._xAxisRenderer; + if (axisRenderer is DateTimeAxisRenderer) { + final DateTimeAxis _axis = axisRenderer._axis; + final DateFormat dateFormat = + _axis.dateFormat ?? axisRenderer._getLabelFormat(axisRenderer); + x = dateFormat.format(DateTime.fromMillisecondsSinceEpoch(xValue)); + } else if (axisRenderer is CategoryAxisRenderer) { + x = dataPoint.x; + } + labelValue = _trackballBehavior.tooltipSettings.format + .replaceAll('point.x', (x ?? xValue).toString()) + .replaceAll( + 'point.y', + _getLabelValue( + yValue, cartesianSeriesRenderer._yAxisRenderer._axis, digits)) + .replaceAll('point.high', highValue.toString()) + .replaceAll('point.low', lowValue.toString()) + .replaceAll('point.open', openValue.toString()) + .replaceAll('point.close', closeValue.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) + .replaceAll('point.size', bubbleSize.toString()) + .replaceAll('point.cumulativeValue', cumulativeValue.toString()); + } else { + labelValue = !cartesianSeriesRenderer._seriesType.contains('range') && + !cartesianSeriesRenderer._seriesType.contains('candle') && + !cartesianSeriesRenderer._seriesType.contains('hilo') && + !cartesianSeriesRenderer._seriesType.contains('boxandwhisker') + ? _getLabelValue( + yValue, cartesianSeriesRenderer._yAxisRenderer._axis, digits) + : cartesianSeriesRenderer._seriesType == 'hiloopenclose' || + cartesianSeriesRenderer._seriesType.contains('candle') || + cartesianSeriesRenderer._seriesType.contains('boxandwhisker') + ? cartesianSeriesRenderer._seriesType.contains('boxandwhisker') + ? 'Maximum : ' + + _getLabelValue(maxValue, cartesianSeriesRenderer._yAxisRenderer._axis) + .toString() + + '\n' + + 'Minimum : ' + + _getLabelValue(minValue, cartesianSeriesRenderer._yAxisRenderer._axis) + .toString() + + '\n' + + 'LowerQuartile : ' + + _getLabelValue(lowerQuartileValue, + cartesianSeriesRenderer._yAxisRenderer._axis) + .toString() + + '\n' + + 'UpperQuartile : ' + + _getLabelValue(upperQuartileValue, + cartesianSeriesRenderer._yAxisRenderer._axis) + .toString() + : 'High : ' + + _getLabelValue(highValue, cartesianSeriesRenderer._yAxisRenderer._axis) + .toString() + + '\n' + + 'Low : ' + + _getLabelValue(lowValue, cartesianSeriesRenderer._yAxisRenderer._axis) + .toString() + + '\n' + + 'Open : ' + + _getLabelValue(openValue, cartesianSeriesRenderer._yAxisRenderer._axis) + .toString() + + '\n' + + 'Close : ' + + _getLabelValue(closeValue, + cartesianSeriesRenderer._yAxisRenderer._axis) + .toString() + : 'High : ' + + _getLabelValue(highValue, cartesianSeriesRenderer._yAxisRenderer._axis) + .toString() + + '\n' + + 'Low : ' + + _getLabelValue(lowValue, cartesianSeriesRenderer._yAxisRenderer._axis) + .toString(); + } + return labelValue; + } + + /// To get header text of trackball + String _getHeaderText(CartesianChartPoint point, + CartesianSeriesRenderer seriesRenderer) { + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; + String headerText; + String date; + if (xAxisRenderer is DateTimeAxisRenderer) { + final DateTimeAxis _xAxis = xAxisRenderer._axis; + final DateFormat dateFormat = + _xAxis.dateFormat ?? xAxisRenderer._getLabelFormat(xAxisRenderer); + date = dateFormat + .format(DateTime.fromMillisecondsSinceEpoch(point.xValue.floor())); + } + headerText = xAxisRenderer is CategoryAxisRenderer + ? point.x.toString() + : xAxisRenderer is DateTimeAxisRenderer + ? date.toString() + : _getLabelValue(point.xValue, xAxisRenderer._axis, + _chart.tooltipBehavior.decimalPlaces) + .toString(); + return headerText; + } /// Performs the double-tap action. /// @@ -422,6 +1715,8 @@ class TrackballBehaviorRenderer with ChartBehavior { /// 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) { _trackballPainter._drawTrackBallLine(canvas, paint, seriesIndex); } @@ -440,3 +1735,15 @@ class TrackballMarkerSettingsRenderer { 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); + + final List points; + final List currentPointIndices; + final List visibleSeriesIndices; + final List visibleSeriesList; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_painter.dart index a65f56118..39026e46e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_painter.dart @@ -20,7 +20,7 @@ class _TrackballPainter extends CustomPainter { bool isTop = false; double borderRadius; Path backgroundPath = Path(); - bool canResetPath = false; + bool canResetPath = true; bool isLeft = false; bool isRight = false; bool enable; @@ -32,19 +32,21 @@ class _TrackballPainter extends CustomPainter { bool isHorizontalOrientation = false; bool isRectSeries = false; TextStyle labelStyle; - List<_ChartPointInfo> chartPointInfo = <_ChartPointInfo>[]; bool divider = true; - final List _markerShapes = []; + List _markerShapes; + //ignore: prefer_final_fields List _tooltipTop = []; + //ignore: prefer_final_fields List _tooltipBottom = []; - List _visibleLocation = []; final List _xAxesInfo = []; final List _yAxesInfo = []; - List<_ClosestPoints> _visiblePoints = <_ClosestPoints>[]; + List<_ChartPointInfo> chartPointInfo; + List<_ClosestPoints> _visiblePoints; _TooltipPositions _tooltipPosition; num _padding = 5; - final num _tooltipPadding = 5; - bool _isTrackLineDrawn = false; + num _tooltipPadding; + bool isRangeSeries; + bool isBoxSeries; @override void paint(Canvas canvas, Size size) => @@ -54,6 +56,12 @@ class _TrackballPainter extends CustomPainter { /// To draw the trackball for all series void _drawTrackball(Canvas canvas) { + chartPointInfo = chartState._trackballBehaviorRenderer._chartPointInfo; + _markerShapes = chartState._trackballBehaviorRenderer._markerShapes; + _visiblePoints = chartState._trackballBehaviorRenderer._visiblePoints; + isRangeSeries = chartState._trackballBehaviorRenderer._isRangeSeries; + isBoxSeries = chartState._trackballBehaviorRenderer._isBoxSeries; + _tooltipPadding = chartState._requireInvertedAxis ? 8 : 5; borderRadius = chart.trackballBehavior.tooltipSettings.borderRadius; pointerLength = chart.trackballBehavior.tooltipSettings.arrowLength; pointerWidth = chart.trackballBehavior.tooltipSettings.arrowWidth; @@ -116,9 +124,9 @@ class _TrackballPainter extends CustomPainter { .seriesRenderer ._seriesType .contains('hilo')) && - !chart.isTransposed) || + !chartState._requireInvertedAxis) || (chartPointInfo[index].seriesRenderer._seriesType.contains('bar') && - chart.isTransposed)) { + chartState._requireInvertedAxis)) { isHorizontalOrientation = true; } isRectSeries = false; @@ -135,9 +143,9 @@ class _TrackballPainter extends CustomPainter { .seriesRenderer ._seriesType .contains('boxandwhisker') && - chart.isTransposed) || + chartState._requireInvertedAxis) || (chartPointInfo[index].seriesRenderer._seriesType.contains('bar') && - !chart.isTransposed)) { + !chartState._requireInvertedAxis)) { isRectSeries = true; } if (!canResetPath && @@ -188,22 +196,22 @@ class _TrackballPainter extends CustomPainter { chartPointInfo[index].header != '') || (chartPointInfo[index].label != null && chartPointInfo[index].label != ''))) { - _calculateTrackballRect(canvas, width, height, index); + _calculateTrackballRect(canvas, width, height, index, chartPointInfo); } else { if (!canResetPath && chartPointInfo[index].label != null && chartPointInfo[index].label != '') { _tooltipTop.add(chartState._requireInvertedAxis ? _visiblePoints[index].closestPointX - - _tooltipPadding - - width / 2 + (_tooltipPadding) - + (width / 2) : _visiblePoints[index].closestPointY - _tooltipPadding - height / 2); _tooltipBottom.add(chartState._requireInvertedAxis ? _visiblePoints[index].closestPointX + - _tooltipPadding + - width / 2 + (_tooltipPadding) + + (width / 2) : _visiblePoints[index].closestPointY + _tooltipPadding + height / 2); @@ -220,71 +228,30 @@ class _TrackballPainter extends CustomPainter { } if (_tooltipTop != null && _tooltipTop.isNotEmpty) { - _tooltipPosition = _smartTooltipPositions( - _tooltipTop, - _tooltipBottom, - _xAxesInfo, - _yAxesInfo, - chartPointInfo, - chartState._requireInvertedAxis); + _tooltipPosition = chartState._trackballBehaviorRenderer + ._smartTooltipPositions( + _tooltipTop, + _tooltipBottom, + _xAxesInfo, + _yAxesInfo, + chartPointInfo, + chartState._requireInvertedAxis, + true); } for (int index = 0; index < chartPointInfo.length; index++) { - if (chart.trackballBehavior.markerSettings != null && - (chart.trackballBehavior.markerSettings.markerVisibility == - TrackballVisibilityMode.auto - ? (chartPointInfo[index] - .seriesRenderer - ._series - .markerSettings - .isVisible) - : chart.trackballBehavior.markerSettings.markerVisibility == - TrackballVisibilityMode.visible)) { - final MarkerSettings markerSettings = - chart.trackballBehavior.markerSettings; - - final DataMarkerType markerType = markerSettings.shape; - final Size size = Size(markerSettings.width, markerSettings.height); - chartPointInfo[index].seriesRenderer._isMarkerRenderEvent = true; - _markerShapes.add(_getMarkerShapesPath( - markerType, - Offset( - chartPointInfo[index].xPosition, - chartPointInfo[index] - .seriesRenderer - ._seriesType - .contains('range') || - chartPointInfo[index] - .seriesRenderer - ._seriesType - .contains('hilo') || - chartPointInfo[index].seriesRenderer._seriesType == - 'candle' - ? chartPointInfo[index].highYPosition - : chartPointInfo[index].seriesRenderer._seriesType == - 'boxandwhisker' - ? chartPointInfo[index].maxYPosition - : chartPointInfo[index].yPosition), - size, - chartPointInfo[index].seriesRenderer, - null, - chart.trackballBehavior)); - } + chartState._trackballBehaviorRenderer._trackballMarker(index); if (_markerShapes != null && _markerShapes.isNotEmpty && _markerShapes.length > index) { - _renderTrackballMarker(chartPointInfo[index].seriesRenderer, canvas, - chart.trackballBehavior, index); + chartState._trackballBehaviorRenderer._renderTrackballMarker( + chartPointInfo[index].seriesRenderer, + canvas, + chart.trackballBehavior, + index); } - final Size size = _getTooltipSize(height, width, index); - height = size.height; - width = size.width; - if (width < 10) { - width = 10; // minimum width for tooltip to render - borderRadius = borderRadius > 5 ? 5 : borderRadius; - } // 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 && @@ -300,10 +267,18 @@ class _TrackballPainter extends CustomPainter { TrackballVisibilityMode.visible) ? (chart.trackballBehavior.markerSettings.width / 2) + 5 : _padding; - if (chart.trackballBehavior.tooltipDisplayMode != + if (chart.trackballBehavior.tooltipSettings.enable && + chart.trackballBehavior.tooltipDisplayMode != TrackballDisplayMode.groupAllPoints && chartPointInfo[index].label != null && chartPointInfo[index].label != '') { + final Size size = _getTooltipSize(height, width, index); + height = size.height; + width = size.width; + if (width < 10) { + width = 10; // minimum width for tooltip to render + borderRadius = borderRadius > 5 ? 5 : borderRadius; + } _calculateTrackballRect( canvas, width, height, index, chartPointInfo, _tooltipPosition); if (index == chartPointInfo.length - 1) { @@ -318,252 +293,6 @@ class _TrackballPainter extends CustomPainter { } } - // Method to place the collided tooltips properly - _TooltipPositions _smartTooltipPositions( - List tooltipTop, - List tooltipBottom, - List _xAxesInfo, - List _yAxesInfo, - List<_ChartPointInfo> chartPointInfo, - bool requireInvertedAxis) { - num tooltipWidth = 0; - _TooltipPositions tooltipPosition; - for (int i = 0; i < chartPointInfo.length; i++) { - if (requireInvertedAxis) { - _visibleLocation.add(chartPointInfo[i].xPosition); - } else { - _visibleLocation.add((chartPointInfo[i] - .seriesRenderer - ._seriesType - .contains('range') || - chartPointInfo[i].seriesRenderer._seriesType.contains('hilo') || - chartPointInfo[i].seriesRenderer._seriesType == 'candle') - ? chartPointInfo[i].highYPosition - : chartPointInfo[i].seriesRenderer._seriesType == 'boxandwhisker' - ? chartPointInfo[i].maxYPosition - : chartPointInfo[i].yPosition); - } - - tooltipWidth += tooltipBottom[i] - tooltipTop[i] + _tooltipPadding; - } - tooltipPosition = _continuousOverlappingPoints( - tooltipTop, tooltipBottom, _visibleLocation); - - if (tooltipWidth < - (chartState._chartAxis._axisClipRect.bottom - - chartState._chartAxis._axisClipRect.top)) { - tooltipPosition = - _verticalArrangements(tooltipPosition, _xAxesInfo, _yAxesInfo); - } - - return tooltipPosition; - } - - _TooltipPositions _verticalArrangements(_TooltipPositions tooltipPPosition, - List _xAxesInfo, List _yAxesInfo) { - final _TooltipPositions tooltipPosition = tooltipPPosition; - num chartHeight, startPos; - final bool isTransposed = chartState._requireInvertedAxis; - dynamic secWidth, width; - final num length = tooltipPosition.tooltipTop.length; - ChartAxisRenderer yAxisRenderer; - final Rect axisClipRect = chartState._chartAxis._axisClipRect; - final num axesLength = - chartState._chartAxis._axisRenderersCollection.length; - for (int i = length - 1; i >= 0; i--) { - yAxisRenderer = _yAxesInfo[i]; - for (int k = 0; k < axesLength; k++) { - if (yAxisRenderer == - chartState._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 < 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 == - chartState._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 (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, - startPoint = 0, - halfHeight, - midPos, - tempTooltipHeight, - temp1TooltipHeight; - int 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 render the trackball marker - void _renderTrackballMarker(CartesianSeriesRenderer seriesRenderer, - Canvas canvas, TrackballBehavior trackballBehavior, int index) { - final CartesianChartPoint point = - seriesRenderer._dataPoints[index]; - final TrackballMarkerSettings markerSettings = - trackballBehavior.markerSettings; - if (markerSettings.shape == DataMarkerType.image) { - _drawImageMarker(null, canvas, chartPointInfo[index].markerXPos, - chartPointInfo[index].markerYPos, markerSettings, chartState); - } - final Paint strokePaint = Paint() - ..color = trackballBehavior.markerSettings.borderWidth == 0 - ? Colors.transparent - : ((point.pointColorMapper != null) - ? point.pointColorMapper - : markerSettings.borderColor ?? seriesRenderer._seriesColor) - ..strokeWidth = markerSettings.borderWidth - ..style = PaintingStyle.stroke; - - final Paint fillPaint = Paint() - ..color = markerSettings.color ?? - (chartState._chartTheme.brightness == Brightness.light - ? Colors.white - : Colors.black) - ..style = PaintingStyle.fill; - canvas.drawPath(_markerShapes[index], strokePaint); - canvas.drawPath(_markerShapes[index], fillPaint); - } - bool headerText = false; bool xFormat = false; bool isColon = true; @@ -626,7 +355,7 @@ class _TrackballPainter extends CustomPainter { } else if ((seriesType.contains('hilo') || seriesType.contains('candle') || seriesType.contains('range') || - seriesType.contains('boxandwhisker')) && + seriesType == 'boxandwhisker') && chartPointInfo[i] .seriesRenderer ._chart @@ -697,12 +426,12 @@ class _TrackballPainter extends CustomPainter { } x = position.dx; if (chart.trackballBehavior.tooltipAlignment == ChartAlignment.center) { - y = chartState._chartAxis._axisClipRect.center.dy; + y = boundaryRect.center.dy; } else if (chart.trackballBehavior.tooltipAlignment == ChartAlignment.near) { - y = chartState._chartAxis._axisClipRect.top; + y = boundaryRect.top; } else { - y = chartState._chartAxis._axisClipRect.bottom; + y = boundaryRect.bottom; } } else { stringValue = []; @@ -729,8 +458,8 @@ class _TrackballPainter extends CustomPainter { ._seriesType .contains('boxandwhisker') || chartPointInfo[index].seriesRenderer._seriesType.contains('hilo')) { - x = chartPointInfo[index].chartDataPoint.markerPoint.x; - y = chartPointInfo[index].chartDataPoint.markerPoint.y; + x = position.dx; + y = position.dy; } else if (chartPointInfo[index].seriesRenderer._seriesType == 'rangearea') { x = chartPointInfo[index].chartDataPoint.markerPoint.x; @@ -771,7 +500,9 @@ class _TrackballPainter extends CustomPainter { _calculateTooltipSize(labelRect, chartPointInfo, tooltipPosition, index); } else { isTop = false; - if (isHorizontalOrientation) { + if (chartPointInfo[index].seriesRenderer._seriesType.contains('bar') + ? chartState._requireInvertedAxis + : chartState._requireInvertedAxis) { xPos = x - (labelRect.width / 2); yPos = (y + pointerLength) + _padding; nosePointX = labelRect.left; @@ -828,11 +559,17 @@ class _TrackballPainter extends CustomPainter { TrackballDisplayMode.groupAllPoints || chart.trackballBehavior.tooltipDisplayMode == TrackballDisplayMode.nearestPoint || - isHorizontalOrientation || - isRectSeries + chart.axes.isNotEmpty ? Rect.fromLTWH(xPos, yPos, labelRect.width, labelRect.height) - : Rect.fromLTWH(xPos, tooltipPosition.tooltipTop[index], - labelRect.width, labelRect.height); + : Rect.fromLTWH( + chartState._requireInvertedAxis + ? tooltipPosition.tooltipTop[index] + : xPos, + !chartState._requireInvertedAxis + ? tooltipPosition.tooltipTop[index] + : yPos, + labelRect.width, + labelRect.height); if (chart.trackballBehavior.tooltipDisplayMode == TrackballDisplayMode.groupAllPoints) { _drawTooltipBackground( @@ -865,20 +602,14 @@ class _TrackballPainter extends CustomPainter { isLeft, isRight, index, - chartPointInfo[index].xPosition, - (chartPointInfo[index] - .seriesRenderer - ._seriesType - .contains('range') || - chartPointInfo[index] - .seriesRenderer - ._seriesType - .contains('hilo') || - chartPointInfo[index].seriesRenderer._seriesType == - 'candle') + isRangeSeries + ? chartPointInfo[index].highXPosition + : isBoxSeries + ? chartPointInfo[index].maxXPosition + : chartPointInfo[index].xPosition, + isRangeSeries ? chartPointInfo[index].highYPosition - : chartPointInfo[index].seriesRenderer._seriesType == - 'boxandwhisker' + : isBoxSeries ? chartPointInfo[index].maxYPosition : chartPointInfo[index].yPosition); } @@ -892,7 +623,9 @@ class _TrackballPainter extends CustomPainter { _TooltipPositions tooltipPositions, int index) { isTop = true; - if (isHorizontalOrientation) { + if (chartPointInfo[index].seriesRenderer._seriesType.contains('bar') + ? chartState._requireInvertedAxis + : chartState._requireInvertedAxis) { xPos = x - (labelRect.width / 2); yPos = (y - labelRect.height) - _padding; nosePointY = labelRect.top - _padding; @@ -937,36 +670,15 @@ class _TrackballPainter extends CustomPainter { /// To draw the line for the trackball void _drawTrackBallLine(Canvas canvas, Paint paint, int index) { final Path dashArrayPath = Path(); - if (chart.trackballBehavior.lineType == TrackballLineType.vertical) { - if (isRectSeries || chart.isTransposed) { - _isTrackLineDrawn = true; - dashArrayPath.moveTo(chartState._trackballBehaviorRenderer._position.dx, - chartState._chartAxis._axisClipRect.top); - dashArrayPath.lineTo(chartState._trackballBehaviorRenderer._position.dx, - chartState._chartAxis._axisClipRect.bottom); - } else { - if (!_isTrackLineDrawn) { - dashArrayPath.moveTo(chartPointInfo[index].xPosition, - chartState._chartAxis._axisClipRect.top); - dashArrayPath.lineTo(chartPointInfo[index].xPosition, - chartState._chartAxis._axisClipRect.bottom); - } - } + if (chartPointInfo[index].seriesRenderer._seriesType.contains('bar') + ? chartState._requireInvertedAxis + : chartState._requireInvertedAxis) { + dashArrayPath.moveTo(boundaryRect.left, chartPointInfo[index].yPosition); + dashArrayPath.lineTo(boundaryRect.right, chartPointInfo[index].yPosition); } else { - if (isRectSeries || chart.isTransposed) { - _isTrackLineDrawn = true; - dashArrayPath.moveTo(chartState._chartAxis._axisClipRect.left, - chartPointInfo[index].yPosition); - dashArrayPath.lineTo(chartState._chartAxis._axisClipRect.right, - chartPointInfo[index].yPosition); - } else { - if (!_isTrackLineDrawn) { - dashArrayPath.moveTo(chartState._chartAxis._axisClipRect.left, - chartState._trackballBehaviorRenderer._position.dy); - dashArrayPath.lineTo(chartState._chartAxis._axisClipRect.right, - chartState._trackballBehaviorRenderer._position.dy); - } - } + dashArrayPath.moveTo(chartPointInfo[index].xPosition, boundaryRect.top); + dashArrayPath.lineTo( + chartPointInfo[index].xPosition, boundaryRect.bottom); } chart.trackballBehavior.lineDashArray != null ? _drawDashedLine( @@ -1050,7 +762,7 @@ class _TrackballPainter extends CustomPainter { TrackballDisplayMode.none) { if (chart.trackballBehavior.tooltipDisplayMode != TrackballDisplayMode.groupAllPoints) { - if (isHorizontalOrientation) { + if (chartState._requireInvertedAxis) { if (isLeft) { startX = rectF.left + borderRadius; endX = startX + pointerWidth; @@ -1060,7 +772,7 @@ class _TrackballPainter extends CustomPainter { } backgroundPath.moveTo( (rectF.left + rectF.width / 2) - pointerWidth, startY); - backgroundPath.lineTo(xPos, yPos); + backgroundPath.lineTo(xPosition, yPosition); backgroundPath.lineTo( (rectF.right - rectF.width / 2) + pointerWidth, endY); } else { @@ -1079,7 +791,6 @@ class _TrackballPainter extends CustomPainter { backgroundPath.lineTo( rectF.left, rectF.bottom - rectF.height / 2 + pointerWidth); backgroundPath.lineTo(rectF.left - pointerLength, yPosition); - backgroundPath.lineTo(rectF.left - pointerLength, yPosition); backgroundPath.lineTo( rectF.left, rectF.top + rectF.height / 2 - pointerWidth); } @@ -1379,826 +1090,6 @@ class _TrackballPainter extends CustomPainter { @override bool shouldRepaint(CustomPainter oldDelegate) => true; - /// calculate trackball points - void _generateAllPoints(Offset position) { - chartPointInfo = <_ChartPointInfo>[]; - _visiblePoints = <_ClosestPoints>[]; - _tooltipTop = []; - _tooltipBottom = []; - _markerShapes?.clear(); - _visibleLocation = []; - final Rect seriesBounds = chartState._chartAxis._axisClipRect; - if (position.dx >= seriesBounds.left && - position.dx <= seriesBounds.right && - position.dy >= seriesBounds.top && - position.dy <= seriesBounds.bottom) { - chartState._trackballBehaviorRenderer._position = position; - double xPos = 0, - yPos = 0, - leastX = 0, - openXPos, - openYPos, - closeXPos, - closeYPos, - cummulativePos, - lowerXPos, - lowerYPos, - upperXPos, - upperYPos, - lowYPos, - highYPos, - minYPos, - maxYPos; - int seriesIndex = 0; - final List seriesAxisRenderers = []; - for (final CartesianSeriesRenderer seriesRenderer - in chartState._seriesRenderers) { - final CartesianSeriesRenderer visibleSeriesRenderer = seriesRenderer; - final ChartAxisRenderer chartAxisRenderer = - visibleSeriesRenderer._xAxisRenderer; - if (chartAxisRenderer == null) { - continue; - } - if (!seriesAxisRenderers.contains(chartAxisRenderer)) { - seriesAxisRenderers.add(chartAxisRenderer); - for (final CartesianSeriesRenderer axisSeriesRenderer - in chartAxisRenderer._seriesRenderers) { - final CartesianSeriesRenderer cartesianSeriesRenderer = - axisSeriesRenderer; - if (axisSeriesRenderer._visible == false || - (axisSeriesRenderer._dataPoints.isEmpty && - !axisSeriesRenderer._isRectSeries)) { - continue; - } - if (cartesianSeriesRenderer._dataPoints.isNotEmpty) { - final List> nearestDataPoints = - _getNearestChartPoints( - position.dx, - position.dy, - chartAxisRenderer, - visibleSeriesRenderer._yAxisRenderer, - cartesianSeriesRenderer); - if (nearestDataPoints == null) { - continue; - } - for (final CartesianChartPoint dataPoint - in nearestDataPoints) { - num yValue; - final int index = - axisSeriesRenderer._dataPoints.indexOf(dataPoint); - final num xValue = - cartesianSeriesRenderer._dataPoints[index].xValue; - if (cartesianSeriesRenderer._seriesType != 'boxandwhisker') { - yValue = cartesianSeriesRenderer._dataPoints[index].yValue; - } - final num minimumValue = - cartesianSeriesRenderer._dataPoints[index].minimum; - final num maximumValue = - cartesianSeriesRenderer._dataPoints[index].maximum; - final num lowerQuartileValue = - cartesianSeriesRenderer._dataPoints[index].lowerQuartile; - final num upperQuartileValue = - cartesianSeriesRenderer._dataPoints[index].upperQuartile; - final num meanValue = - cartesianSeriesRenderer._dataPoints[index].mean; - final num highValue = - cartesianSeriesRenderer._dataPoints[index].high; - final num lowValue = - cartesianSeriesRenderer._dataPoints[index].low; - final num openValue = - cartesianSeriesRenderer._dataPoints[index].open; - final num closeValue = - cartesianSeriesRenderer._dataPoints[index].close; - final String seriesName = - cartesianSeriesRenderer._series.name ?? - 'Series $seriesIndex'; - final num bubbleSize = - cartesianSeriesRenderer._dataPoints[index].bubbleSize; - final num cumulativeValue = - cartesianSeriesRenderer._dataPoints[index].cumulativeValue; - final Rect axisClipRect = _calculatePlotOffset( - cartesianSeriesRenderer - ._chartState._chartAxis._axisClipRect, - Offset( - cartesianSeriesRenderer._xAxisRenderer._axis.plotOffset, - cartesianSeriesRenderer - ._yAxisRenderer._axis.plotOffset)); - xPos = _calculatePoint( - xValue, - yValue, - cartesianSeriesRenderer._xAxisRenderer, - cartesianSeriesRenderer._yAxisRenderer, - chartState._requireInvertedAxis, - cartesianSeriesRenderer._series, - axisClipRect) - .x; - cummulativePos = _calculatePoint( - xValue, - cumulativeValue, - cartesianSeriesRenderer._xAxisRenderer, - cartesianSeriesRenderer._yAxisRenderer, - chartState._requireInvertedAxis, - cartesianSeriesRenderer._series, - axisClipRect) - .y; - - if (!xPos.toDouble().isNaN) { - if (seriesIndex == 0 || - ((leastX - position.dx) > (leastX - xPos))) { - leastX = xPos; - } - final String labelValue = _getTrackballLabelText( - cartesianSeriesRenderer, - xValue, - yValue, - lowValue, - highValue, - openValue, - closeValue, - minimumValue, - maximumValue, - lowerQuartileValue, - upperQuartileValue, - meanValue, - seriesName, - bubbleSize, - cumulativeValue, - dataPoint); - yPos = cartesianSeriesRenderer._seriesType - .contains('stackedline') || - cartesianSeriesRenderer._seriesType - .contains('stackedarea') - ? cummulativePos - : _calculatePoint( - xValue, - yValue, - cartesianSeriesRenderer._xAxisRenderer, - cartesianSeriesRenderer._yAxisRenderer, - chartState._requireInvertedAxis, - cartesianSeriesRenderer._series, - axisClipRect) - .y; - if (cartesianSeriesRenderer._seriesType.contains('range') || - cartesianSeriesRenderer._seriesType.contains('hilo') || - cartesianSeriesRenderer._seriesType == 'candle') { - lowYPos = _calculatePoint( - xValue, - lowValue, - cartesianSeriesRenderer._xAxisRenderer, - cartesianSeriesRenderer._yAxisRenderer, - chartState._requireInvertedAxis, - cartesianSeriesRenderer._series, - axisClipRect) - .y; - - highYPos = _calculatePoint( - xValue, - highValue, - cartesianSeriesRenderer._xAxisRenderer, - cartesianSeriesRenderer._yAxisRenderer, - chartState._requireInvertedAxis, - cartesianSeriesRenderer._series, - axisClipRect) - .y; - - if (cartesianSeriesRenderer._seriesType == - 'hiloopenclose' || - cartesianSeriesRenderer._seriesType == 'candle') { - openXPos = dataPoint.openPoint.x; - openYPos = dataPoint.openPoint.y; - closeXPos = dataPoint.closePoint.x; - closeYPos = dataPoint.closePoint.y; - } - } else if (cartesianSeriesRenderer._seriesType - .contains('boxandwhisker')) { - minYPos = _calculatePoint( - xValue, - minimumValue, - cartesianSeriesRenderer._xAxisRenderer, - cartesianSeriesRenderer._yAxisRenderer, - chartState._requireInvertedAxis, - cartesianSeriesRenderer._series, - axisClipRect) - .y; - - maxYPos = _calculatePoint( - xValue, - maximumValue, - cartesianSeriesRenderer._xAxisRenderer, - cartesianSeriesRenderer._yAxisRenderer, - chartState._requireInvertedAxis, - cartesianSeriesRenderer._series, - axisClipRect) - .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, yPos - 1, 2, 2)); - if (seriesBounds.contains(Offset(xPos, yPos)) || - seriesBounds.overlaps(rect)) { - !chartState._requireInvertedAxis - ? _visiblePoints.add(_ClosestPoints( - closestPointX: xPos, - closestPointY: (cartesianSeriesRenderer._seriesType - .contains('range') || - cartesianSeriesRenderer._seriesType - .contains('hilo') || - cartesianSeriesRenderer._seriesType == - 'candle') - ? highYPos - : cartesianSeriesRenderer._seriesType == - 'boxandwhisker' - ? maxYPos - : yPos)) - : _visiblePoints.add(_ClosestPoints( - closestPointX: xPos + axisClipRect.bottom, - closestPointY: yPos + axisClipRect.right)); - _addChartPointInfo( - cartesianSeriesRenderer, - xPos, - yPos, - index, - labelValue, - seriesIndex, - lowYPos, - highYPos, - openXPos, - openYPos, - closeXPos, - closeYPos, - minYPos, - maxYPos, - lowerXPos, - lowerYPos, - upperXPos, - upperYPos); - if (chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints && - leastX >= seriesBounds.left) { - chartState._requireInvertedAxis - ? yPos = leastX - : xPos = leastX; - } - } - } - } - seriesIndex++; - } - _validateNearestXValue( - leastX, cartesianSeriesRenderer, position.dx, position.dy); - } - - if (_visiblePoints.isNotEmpty) { - chartState._requireInvertedAxis - ? _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 (chart.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.groupAllPoints) { - chartState._requireInvertedAxis - ? chartPointInfo.sort( - (_ChartPointInfo a, _ChartPointInfo b) => - a.xPosition.compareTo(b.xPosition)) - : chartPointInfo.sort( - (_ChartPointInfo a, _ChartPointInfo b) => - a.yPosition.compareTo(b.yPosition)); - } - if (chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.nearestPoint || - (seriesRenderer._isRectSeries && - chart.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.groupAllPoints)) { - _validateNearestPointForAllSeries( - leastX, chartPointInfo, position.dx, position.dy); - } - } - } - } - _triggerTrackballEvent(); - } - chartPointInfo = _getValidPoints(chartPointInfo, position); - } - - /// To get valid points of trackball - List<_ChartPointInfo> _getValidPoints( - List<_ChartPointInfo> points, Offset position) { - final List<_ChartPointInfo> validPoints = <_ChartPointInfo>[]; - for (final _ChartPointInfo point in points) { - if (validPoints.isEmpty) { - validPoints.add(point); - } else if (validPoints[0].seriesRenderer._xAxisRenderer == - point.seriesRenderer._xAxisRenderer) { - if (!point.seriesRenderer._chartState._requireInvertedAxis) { - if ((validPoints[0].xPosition - position.dx).abs() == - (point.xPosition - position.dx).abs()) { - validPoints.add(point); - } - } else if ((validPoints[0].yPosition - position.dy).abs() == - (point.yPosition - position.dy).abs()) { - validPoints.add(point); - } - } else if ((validPoints[0].xPosition - position.dx).abs() > - (point.xPosition - position.dx).abs()) { - validPoints.clear(); - validPoints.add(point); - } else if ((validPoints[0].xPosition - position.dx).abs() == - (point.xPosition - position.dx).abs()) { - if ((validPoints[0].yPosition - position.dy).abs() > - (point.yPosition - position.dy).abs()) { - validPoints.clear(); - validPoints.add(point); - } - } - } - return validPoints; - } - - /// To get and return label text of the trackball - String _getTrackballLabelText( - CartesianSeriesRenderer cartesianSeriesRenderer, - num xValue, - num yValue, - num lowValue, - num highValue, - num openValue, - num closeValue, - num minValue, - num maxValue, - num lowerQuartileValue, - num upperQuartileValue, - num meanValue, - String seriesName, - num bubbleSize, - num cumulativeValue, - CartesianChartPoint dataPoint) { - String labelValue; - final int digits = chart.trackballBehavior.tooltipSettings.decimalPlaces; - if (chart.trackballBehavior.tooltipSettings.format != null) { - dynamic x; - final ChartAxisRenderer axisRenderer = - cartesianSeriesRenderer._xAxisRenderer; - if (axisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis _axis = axisRenderer._axis; - final DateFormat dateFormat = - _axis.dateFormat ?? axisRenderer._getLabelFormat(axisRenderer); - x = dateFormat.format(DateTime.fromMillisecondsSinceEpoch(xValue)); - } else if (axisRenderer is CategoryAxisRenderer) { - x = dataPoint.x; - } - labelValue = chart.trackballBehavior.tooltipSettings.format - .replaceAll('point.x', (x ?? xValue).toString()) - .replaceAll( - 'point.y', - _getLabelValue( - yValue, cartesianSeriesRenderer._yAxisRenderer._axis, digits)) - .replaceAll('point.high', highValue.toString()) - .replaceAll('point.low', lowValue.toString()) - .replaceAll('point.open', openValue.toString()) - .replaceAll('point.close', closeValue.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) - .replaceAll('point.size', bubbleSize.toString()) - .replaceAll('point.cumulativeValue', cumulativeValue.toString()); - } else { - labelValue = !cartesianSeriesRenderer._seriesType.contains('range') && - !cartesianSeriesRenderer._seriesType.contains('candle') && - !cartesianSeriesRenderer._seriesType.contains('hilo') && - !cartesianSeriesRenderer._seriesType.contains('boxandwhisker') - ? _getLabelValue( - yValue, cartesianSeriesRenderer._yAxisRenderer._axis, digits) - : cartesianSeriesRenderer._seriesType == 'hiloopenclose' || - cartesianSeriesRenderer._seriesType.contains('candle') || - cartesianSeriesRenderer._seriesType.contains('boxandwhisker') - ? cartesianSeriesRenderer._seriesType.contains('boxandwhisker') - ? 'Maximum : ' + - _getLabelValue(maxValue, cartesianSeriesRenderer._yAxisRenderer._axis) - .toString() + - '\n' + - 'Minimum : ' + - _getLabelValue(minValue, cartesianSeriesRenderer._yAxisRenderer._axis) - .toString() + - '\n' + - 'LowerQuartile : ' + - _getLabelValue(lowerQuartileValue, - cartesianSeriesRenderer._yAxisRenderer._axis) - .toString() + - '\n' + - 'UpperQuartile : ' + - _getLabelValue(upperQuartileValue, - cartesianSeriesRenderer._yAxisRenderer._axis) - .toString() - : 'High : ' + - _getLabelValue(highValue, cartesianSeriesRenderer._yAxisRenderer._axis) - .toString() + - '\n' + - 'Low : ' + - _getLabelValue(lowValue, cartesianSeriesRenderer._yAxisRenderer._axis) - .toString() + - '\n' + - 'Open : ' + - _getLabelValue(openValue, cartesianSeriesRenderer._yAxisRenderer._axis) - .toString() + - '\n' + - 'Close : ' + - _getLabelValue(closeValue, - cartesianSeriesRenderer._yAxisRenderer._axis) - .toString() - : 'High : ' + - _getLabelValue(highValue, cartesianSeriesRenderer._yAxisRenderer._axis) - .toString() + - '\n' + - 'Low : ' + - _getLabelValue(lowValue, cartesianSeriesRenderer._yAxisRenderer._axis) - .toString(); - } - return labelValue; - } - - /// Event for trackball render - void _triggerTrackballEvent() { - if (chart.onTrackballPositionChanging != null) { - chartState._chartPointInfo = chartState - ._trackballBehaviorRenderer._trackballPainter.chartPointInfo; - for (int index = 0; index < chartState._chartPointInfo.length; index++) { - TrackballArgs chartPoint; - chartPoint = TrackballArgs(); - chartPoint.chartPointInfo = chartState._chartPointInfo[index]; - chart.onTrackballPositionChanging(chartPoint); - chartState._chartPointInfo[index].label = - chartPoint.chartPointInfo.label; - chartState._chartPointInfo[index].header = - chartPoint.chartPointInfo.header; - } - } - } - - /// To validate the nearest point in all series for trackball - void _validateNearestPointForAllSeries(double leastX, - List<_ChartPointInfo> trackballInfo, double touchXPos, double touchYPos) { - double xPos = 0; - final List<_ChartPointInfo> tempTrackballInfo = - List<_ChartPointInfo>.from(trackballInfo); - for (int i = 0; i < tempTrackballInfo.length; i++) { - final _ChartPointInfo pointInfo = tempTrackballInfo[i]; - num yValue; - final num xValue = - pointInfo.seriesRenderer._dataPoints[pointInfo.dataPointIndex].xValue; - if (pointInfo.seriesRenderer._seriesType != 'boxandwhisker') { - yValue = pointInfo - .seriesRenderer._dataPoints[pointInfo.dataPointIndex].yValue; - } - final Rect axisClipRect = _calculatePlotOffset( - pointInfo.seriesRenderer._chartState._chartAxis._axisClipRect, - Offset(pointInfo.seriesRenderer._xAxisRenderer._axis.plotOffset, - pointInfo.seriesRenderer._yAxisRenderer._axis.plotOffset)); - - xPos = _calculatePoint( - xValue, - yValue, - pointInfo.seriesRenderer._xAxisRenderer, - pointInfo.seriesRenderer._yAxisRenderer, - chartState._requireInvertedAxis, - pointInfo.seriesRenderer._series, - axisClipRect) - .x; - trackballInfo = _getRectSeriesPointInfo(trackballInfo, pointInfo, xValue, - yValue, touchXPos, leastX, axisClipRect); - if (chart.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.floatAllPoints && - (!pointInfo.seriesRenderer._chartState._requireInvertedAxis)) { - if (leastX != xPos) { - trackballInfo.remove(pointInfo); - } - final int pointInfoIndex = tempTrackballInfo.indexOf(pointInfo); - final double yPos = touchYPos; - if (pointInfoIndex < tempTrackballInfo.length - 1) { - final _ChartPointInfo nextPointInfo = - tempTrackballInfo[pointInfoIndex + 1]; - - if (nextPointInfo.yPosition == pointInfo.yPosition || - (pointInfo.yPosition > yPos && pointInfoIndex == 0)) { - continue; - } - if (!(yPos < - (nextPointInfo.yPosition - - ((nextPointInfo.yPosition - pointInfo.yPosition) / 2)))) { - trackballInfo.remove(pointInfo); - } else if (pointInfoIndex != 0) { - final _ChartPointInfo previousPointInfo = - tempTrackballInfo[pointInfoIndex - 1]; - if (yPos < - (pointInfo.yPosition - - ((pointInfo.yPosition - previousPointInfo.yPosition) / - 2))) { - trackballInfo.remove(pointInfo); - } - } - } else { - if (pointInfoIndex != 0 && - pointInfoIndex == tempTrackballInfo.length - 1) { - final _ChartPointInfo previousPointInfo = - tempTrackballInfo[pointInfoIndex - 1]; - if (yPos < previousPointInfo.yPosition) { - trackballInfo.remove(pointInfo); - } - if (yPos < - (pointInfo.yPosition - - ((pointInfo.yPosition - previousPointInfo.yPosition) / - 2))) { - trackballInfo.remove(pointInfo); - } - } - } - } - } - } - - /// It returns the trackball information for all series - List<_ChartPointInfo> _getRectSeriesPointInfo( - List<_ChartPointInfo> trackballInfo, - _ChartPointInfo pointInfo, - num xValue, - num yValue, - double touchXPos, - double leastX, - Rect axisClipRect) { - if (pointInfo.seriesRenderer._isRectSeries && - chart.enableSideBySideSeriesPlacement && - chart.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.groupAllPoints) { - final bool isXAxisInverse = chartState._requireInvertedAxis; - final _VisibleRange sideBySideInfo = - _calculateSideBySideInfo(pointInfo.seriesRenderer, chartState); - final double xStartValue = xValue + sideBySideInfo.minimum; - final double xEndValue = xValue + sideBySideInfo.maximum; - double xStart = _calculatePoint( - xStartValue, - yValue, - pointInfo.seriesRenderer._xAxisRenderer, - pointInfo.seriesRenderer._yAxisRenderer, - chartState._requireInvertedAxis, - pointInfo.seriesRenderer._series, - axisClipRect) - .x; - double xEnd = _calculatePoint( - xEndValue, - yValue, - pointInfo.seriesRenderer._xAxisRenderer, - pointInfo.seriesRenderer._yAxisRenderer, - chartState._requireInvertedAxis, - pointInfo.seriesRenderer._series, - axisClipRect) - .x; - bool isStartIndex = pointInfo.seriesRenderer._sideBySideIndex == 0; - bool isEndIndex = pointInfo.seriesRenderer._sideBySideIndex == - pointInfo.seriesRenderer._chartState._chartSeries - .visibleSeriesRenderers.length - - 1; - final double xPos = touchXPos; - if (isXAxisInverse) { - final double temp = xStart; - xStart = xEnd; - xEnd = temp; - final bool isTemp = isEndIndex; - isEndIndex = isStartIndex; - isStartIndex = isTemp; - } else if (pointInfo.seriesRenderer._chartState._zoomedState == true || - chart.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.floatAllPoints) { - if (xPos < leastX && isStartIndex) { - if (!(xPos < xStart) && !(xPos < xEnd && xPos >= xStart)) { - trackballInfo.remove(pointInfo); - } - } else if (xPos > leastX && isEndIndex) { - if (!(xPos > xEnd && xPos > xStart) && - !(xPos < xEnd && xPos >= xStart)) { - trackballInfo.remove(pointInfo); - } - } else if (!(xPos < xEnd && xPos >= xStart)) { - trackballInfo.remove(pointInfo); - } - } - } - return trackballInfo; - } - - /// To find the nearest x value to render a trackball - void _validateNearestXValue( - double leastX, - CartesianSeriesRenderer seriesRenderer, - double touchXPos, - double touchYPos) { - final List<_ChartPointInfo> leastPointInfo = <_ChartPointInfo>[]; - final Rect axisClipRect = _calculatePlotOffset( - seriesRenderer._chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer._axis.plotOffset, - seriesRenderer._yAxisRenderer._axis.plotOffset)); - - double nearPointX = seriesRenderer._chartState._requireInvertedAxis - ? axisClipRect.top - : axisClipRect.left; - final double touchXValue = - seriesRenderer._chartState._requireInvertedAxis ? touchYPos : touchXPos; - double delta = 0; - for (final _ChartPointInfo pointInfo in chartPointInfo) { - if (pointInfo.dataPointIndex < seriesRenderer._dataPoints.length) { - num yValue; - final num xValue = - seriesRenderer._dataPoints[pointInfo.dataPointIndex].xValue; - if (seriesRenderer._seriesType != 'boxandwhisker') { - yValue = seriesRenderer._dataPoints[pointInfo.dataPointIndex].yValue; - } - final double currX = seriesRenderer._chartState._requireInvertedAxis - ? _calculatePoint( - xValue, - yValue, - pointInfo.seriesRenderer._xAxisRenderer, - pointInfo.seriesRenderer._yAxisRenderer, - chartState._requireInvertedAxis, - pointInfo.seriesRenderer._series, - axisClipRect) - .y - : _calculatePoint( - xValue, - yValue, - pointInfo.seriesRenderer._xAxisRenderer, - pointInfo.seriesRenderer._yAxisRenderer, - chartState._requireInvertedAxis, - pointInfo.seriesRenderer._series, - axisClipRect) - .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 < - seriesRenderer._dataPoints.length) { - leastX = _getLeastX(chartPointInfo[0], seriesRenderer, axisClipRect); - } - } - - if (chart.isTransposed) { - yPos = leastX; - } else { - xPos = leastX; - } - } - } - - /// To get the lowest X value to render trackball - double _getLeastX(_ChartPointInfo pointInfo, - CartesianSeriesRenderer seriesRenderer, Rect axisClipRect) { - return _calculatePoint( - seriesRenderer._dataPoints[pointInfo.dataPointIndex].xValue, - 0, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - chartState._requireInvertedAxis, - seriesRenderer._series, - axisClipRect) - .x; - } - - /// To add chart point info - void _addChartPointInfo(CartesianSeriesRenderer seriesRenderer, double xPos, - double yPos, int dataPointIndex, String label, num seriesIndex, - [double lowYPos, - double highYPos, - double openXPos, - double openYPos, - double closeXPos, - double closeYPos, - double minYPos, - double maxYPos, - double lowerXPos, - double lowerYPos, - double upperXPos, - double upperYPos]) { - final _ChartPointInfo pointInfo = _ChartPointInfo(); - - pointInfo.seriesRenderer = seriesRenderer; - pointInfo.series = seriesRenderer._series; - pointInfo.markerXPos = xPos; - pointInfo.markerYPos = yPos; - pointInfo.xPosition = xPos; - pointInfo.yPosition = yPos; - pointInfo.seriesIndex = seriesIndex; - - if (seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType == 'candle') { - pointInfo.lowYPosition = lowYPos; - pointInfo.highYPosition = highYPos; - if (seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType == 'candle') { - pointInfo.openXPosition = openXPos; - pointInfo.openYPosition = openYPos; - pointInfo.closeXPosition = closeXPos; - pointInfo.closeYPosition = closeYPos; - } - } else if (seriesRenderer._seriesType.contains('boxandwhisker')) { - pointInfo.minYPosition = minYPos; - pointInfo.maxYPosition = maxYPos; - - pointInfo.lowerXPosition = lowerXPos; - pointInfo.lowerYPosition = lowerYPos; - pointInfo.upperXPosition = upperXPos; - pointInfo.upperYPosition = upperYPos; - } - - if (seriesRenderer._segments.length > dataPointIndex) { - pointInfo.color = seriesRenderer._segments[dataPointIndex]._color; - } else if (seriesRenderer._segments.length > 1) { - pointInfo.color = - seriesRenderer._segments[seriesRenderer._segments.length - 1]._color; - } - pointInfo.chartDataPoint = seriesRenderer._dataPoints[dataPointIndex]; - pointInfo.dataPointIndex = dataPointIndex; - pointInfo.label = label; - pointInfo.header = getHeaderText( - seriesRenderer._dataPoints[dataPointIndex], seriesRenderer); - chartPointInfo.add(pointInfo); - } - - /// To show track ball based on point index - void showTrackball( - List visibleSeriesRenderers, int pointIndex) { - _ChartLocation position; - final CartesianSeriesRenderer seriesRenderer = visibleSeriesRenderers[0]; - final Rect rect = seriesRenderer._chartState._chartAxis._axisClipRect; - final List> _dataPoints = - >[]; - for (int i = 0; i < seriesRenderer._dataPoints.length; i++) { - if (seriesRenderer._dataPoints[i].isGap != true) { - _dataPoints.add(seriesRenderer._dataPoints[i]); - } - } - if (pointIndex != null && - pointIndex.abs() < seriesRenderer._dataPoints.length) { - final int index = pointIndex; - final num xValue = seriesRenderer._dataPoints[index].xValue; - final num yValue = - seriesRenderer._series is _FinancialSeriesBase || - seriesRenderer._seriesType.contains('range') - ? seriesRenderer._dataPoints[index].high - : seriesRenderer._dataPoints[index].yValue; - position = _calculatePoint( - xValue, - yValue, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, - seriesRenderer._series, - rect); - _generateAllPoints(Offset(position.x, position.y)); - } - } - - /// To get header text of trackball - String getHeaderText(CartesianChartPoint point, - CartesianSeriesRenderer seriesRenderer) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - String headerText; - String date; - if (xAxisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis _xAxis = xAxisRenderer._axis; - final DateFormat dateFormat = - _xAxis.dateFormat ?? xAxisRenderer._getLabelFormat(xAxisRenderer); - date = dateFormat - .format(DateTime.fromMillisecondsSinceEpoch(point.xValue.floor())); - } - headerText = xAxisRenderer is CategoryAxisRenderer - ? point.x.toString() - : xAxisRenderer is DateTimeAxisRenderer - ? date.toString() - : _getLabelValue(point.xValue, xAxisRenderer._axis, - chart.tooltipBehavior.decimalPlaces) - .toString(); - return headerText; - } - /// 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 new file mode 100644 index 000000000..4b25021a7 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_template.dart @@ -0,0 +1,533 @@ +part of charts; + +// Widget class which is used to display the trackball template +class _TrackballTemplate extends StatefulWidget { + const _TrackballTemplate({Key key, this.chartState, this.trackballBehavior}) + : super(key: key); + + final SfCartesianChartState chartState; + final TrackballBehavior trackballBehavior; + + @override + State createState() { + return _TrackballTemplateState(); + } +} + +class _TrackballTemplateState extends State<_TrackballTemplate> { + bool _isRender = false; + + //ignore: unused_field + _TrackballTemplateState _state; + List<_ChartPointInfo> _chartPointInfo; + List _markerShapes; + TrackballGroupingModeInfo groupingModeInfo; + Widget _template; + //ignore: unused_field + double _duration; + //ignore: unused_field + bool _alwaysShow; + //ignore: unused_field + Timer _trackballTimer; + 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 && _chartPointInfo != null && _chartPointInfo.isNotEmpty) { + for (int index = 0; index < _chartPointInfo.length; index++) { + seriesType = _chartPointInfo[index].seriesRenderer._seriesType; + _isRangeSeries = seriesType.contains('range') || + seriesType.contains('hilo') || + seriesType == 'candle'; + _isBoxSeries = seriesType == 'boxandwhisker'; + if (widget.trackballBehavior.tooltipDisplayMode == + TrackballDisplayMode.groupAllPoints) { + _template = widget.trackballBehavior.builder(context, + TrackballDetails(null, null, null, null, groupingModeInfo)); + trackballWidget = _TrackballRenderObject( + child: _template, + template: _template, + chartState: widget.chartState, + 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] + .seriesRenderer + ._dataPoints[_chartPointInfo[index].dataPointIndex], + _chartPointInfo[index].seriesRenderer._series, + _chartPointInfo[index].dataPointIndex, + _chartPointInfo[index].seriesIndex, + null)); + + trackballWidget = _TrackballRenderObject( + child: _template, + template: _template, + chartState: widget.chartState, + xPos: _chartPointInfo[index].xPosition, + 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: [ + Stack(children: trackballWidgets), + CustomPaint( + painter: _TracklinePainter(widget.trackballBehavior, + widget.chartState, _chartPointInfo, _markerShapes)) + ]); + } else { + trackballWidget = Container(); + return trackballWidget; + } + } + + @override + void dispose() { + super.dispose(); + } + + void refresh() { + setState(() { + _isRender = true; + }); + } + + /// To hide tooltip templates + void hideTrackballTemplate() { + if (mounted && !_alwaysShow) { + setState(() { + _isRender = false; + }); + } + } +} + +class _TrackballRenderObject extends SingleChildRenderObjectWidget { + _TrackballRenderObject( + {Key key, + Widget child, + this.template, + this.chartState, + this.xPos, + this.yPos, + this.trackballBehavior, + this.chartPointInfo, + this.index}) + : super(key: key, child: child); + + final Widget template; + final int index; + final SfCartesianChartState chartState; + final List<_ChartPointInfo> chartPointInfo; + final double xPos; + final double yPos; + final TrackballBehavior trackballBehavior; + + @override + RenderObject createRenderObject(BuildContext context) { + return _TrackballTemplateRenderBox( + template, chartState, 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 { + _TrackballTemplateRenderBox( + this._template, this._chartState, this.xPos, this.yPos, + [this.chartPointInfo, this.index, RenderBox child]) + : super(child); + + Widget _template; + final SfCartesianChartState _chartState; + double xPos, yPos; + List<_ChartPointInfo> chartPointInfo; + int index; + double pointerLength, pointerWidth; + Rect trackballTemplateRect; + Rect boundaryRect; + num padding = 10; + TrackballBehavior trackballBehavior; + bool isGroupAllPoints, isNearestPoint; + Widget get template => _template; + bool isRight = false, isBottom = false; + bool isTemplateInBounds = true; + Offset arrowOffset; + _TooltipPositions tooltipPosition; + BoxParentData childParentData; + set template(Widget value) { + if (_template != value) { + _template = value; + markNeedsLayout(); + } + } + + @override + void performLayout() { + trackballBehavior = _chartState._chart.trackballBehavior; + isGroupAllPoints = trackballBehavior.tooltipDisplayMode == + TrackballDisplayMode.groupAllPoints; + isNearestPoint = trackballBehavior.tooltipDisplayMode == + TrackballDisplayMode.nearestPoint; + final TrackballBehaviorRenderer trackballBehaviorRenderer = + _chartState._trackballBehaviorRenderer; + final List tooltipTop = trackballBehaviorRenderer._tooltipTop; + final List tooltipBottom = trackballBehaviorRenderer._tooltipBottom; + final List<_ClosestPoints> visiblePoints = + trackballBehaviorRenderer._visiblePoints; + final List xAxesInfo = + trackballBehaviorRenderer._xAxesInfo; + final List yAxesInfo = + trackballBehaviorRenderer._yAxesInfo; + boundaryRect = _chartState._chartAxis._axisClipRect; + final num totalWidth = boundaryRect.left + boundaryRect.width; + tooltipPosition = trackballBehaviorRenderer._tooltipPosition; + final 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; + + 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 (index == 0) { + for (int index = 0; index < chartPointInfo.length; index++) { + tooltipTop.add(_chartState._requireInvertedAxis + ? visiblePoints[index].closestPointX - (size.width / 2) + : visiblePoints[index].closestPointY - size.height / 2); + tooltipBottom.add(_chartState._requireInvertedAxis + ? visiblePoints[index].closestPointX + (size.width / 2) + : visiblePoints[index].closestPointY + size.height / 2); + xAxesInfo.add(chartPointInfo[index].seriesRenderer._xAxisRenderer); + yAxesInfo.add(chartPointInfo[index].seriesRenderer._yAxisRenderer); + } + } + + if (tooltipTop != null && tooltipTop.isNotEmpty) { + tooltipPosition = trackballBehaviorRenderer._smartTooltipPositions( + tooltipTop, + tooltipBottom, + xAxesInfo, + yAxesInfo, + chartPointInfo, + _chartState._requireInvertedAxis); + } + + if (!isGroupAllPoints) { + left = _chartState._requireInvertedAxis + ? tooltipPosition.tooltipTop[index] + : xPos + + padding + + (isTrackballMarkerEnabled + ? trackballBehavior.markerSettings.width / 2 + : 0); + top = _chartState._requireInvertedAxis + ? yPos + + pointerLength + + (isTrackballMarkerEnabled + ? trackballBehavior.markerSettings.width / 2 + : 0) + : tooltipPosition.tooltipTop[index]; + + if (isNearestPoint) { + left = _chartState._requireInvertedAxis + ? xPos + size.width / 2 + : xPos + + padding + + (isTrackballMarkerEnabled + ? trackballBehavior.markerSettings.width / 2 + : 0); + top = _chartState._requireInvertedAxis + ? yPos + + padding + + (isTrackballMarkerEnabled + ? trackballBehavior.markerSettings.width / 2 + : 0) + : yPos - size.height / 2; + } + + if (!_chartState._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 - + size.height - + (isTrackballMarkerEnabled + ? (trackballBehavior.markerSettings.height) + : 0); + } else { + isBottom = false; + } + } + trackballTemplateRect = + Rect.fromLTWH(left, top, size.width, size.height); + + if (_isTemplateWithinBounds( + boundaryRect, trackballTemplateRect, offset)) { + isTemplateInBounds = true; + childParentData.offset = Offset(left, top); + } 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); + + if (_isTemplateWithinBounds( + boundaryRect, trackballTemplateRect, offset)) { + isTemplateInBounds = true; + childParentData.offset = Offset( + xPos + + padding + + (isTrackballMarkerEnabled + ? trackballBehavior.markerSettings.width / 2 + : 0), + 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) { + super.paint(context, offset); + + 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].seriesRenderer._series.color ?? + _chartState._chartTheme.crosshairBackgroundColor) + ..isAntiAlias = false + ..style = PaintingStyle.fill; + final Paint strokePaint = Paint() + ..color = trackballBehavior.tooltipSettings.borderColor ?? + (chartPointInfo[index].seriesRenderer._series.color ?? + _chartState._chartTheme.crosshairBackgroundColor) + ..strokeWidth = trackballBehavior.tooltipSettings.borderWidth + ..strokeCap = StrokeCap.butt + ..isAntiAlias = false + ..style = PaintingStyle.stroke; + final Path path = Path(); + if (!_chartState._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) { + 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 _TracklinePainter extends CustomPainter { + _TracklinePainter(this.trackballBehavior, this.chartState, + this.chartPointInfo, this.markerShapes); + + TrackballBehavior trackballBehavior; + SfCartesianChartState chartState; + List<_ChartPointInfo> chartPointInfo; + List markerShapes; + bool isTrackLineDrawn = false; + + @override + void paint(Canvas canvas, Size size) { + final Path dashArrayPath = Path(); + final Paint trackballLinePaint = Paint(); + trackballLinePaint.color = trackballBehavior.lineColor ?? + chartState._chartTheme.crosshairLineColor; + trackballLinePaint.strokeWidth = trackballBehavior.lineWidth; + trackballLinePaint.style = PaintingStyle.stroke; + trackballBehavior.lineWidth == 0 + ? trackballLinePaint.color = Colors.transparent + : trackballLinePaint.color = trackballLinePaint.color; + final Rect boundaryRect = chartState._chartAxis._axisClipRect; + + if (chartPointInfo != null && chartPointInfo.isNotEmpty) { + for (int index = 0; index < chartPointInfo.length; index++) { + if (index == 0) { + if (chartPointInfo[index].seriesRenderer._seriesType.contains('bar') + ? chartState._requireInvertedAxis + : chartState._requireInvertedAxis) { + dashArrayPath.moveTo( + boundaryRect.left, chartPointInfo[index].yPosition); + dashArrayPath.lineTo( + boundaryRect.right, chartPointInfo[index].yPosition); + } 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) { + chartState._trackballBehaviorRenderer._renderTrackballMarker( + chartPointInfo[index].seriesRenderer, + canvas, + trackballBehavior, + index); + } + } + } + } + + @override + bool shouldRepaint(_TracklinePainter oldDelegate) => true; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_panning.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_panning.dart index b89635662..896d52928 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_panning.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_panning.dart @@ -383,7 +383,6 @@ class ZoomPanBehavior { _bindZoomEvent(chart, axisRenderer, zoomResetArgs, chart.onZoomReset); } } - chartState._isLegendToggled = false; zoomPanBehaviorRenderer._createZoomState(); } } @@ -450,10 +449,11 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { axisRenderer._bounds = const Rect.fromLTWH(0, 0, 0, 0); axisRenderer._visibleLabels = []; } - if (zoomFactor < (_zoomPanBehavior.maximumZoomLevel ?? 0.1)) { - axisRenderer._zoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; + final num maxZoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; + if (zoomFactor < maxZoomFactor) { + axisRenderer._zoomFactor = maxZoomFactor; axisRenderer._zoomPosition = 0.0; - zoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; + zoomFactor = maxZoomFactor; } } @@ -710,6 +710,12 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { (selectionMax - selectionMin) / _zoomAxes[axisIndex].actualDelta; currentZoomPosition = currentPosition < 0 ? 0 : currentPosition; currentZoomFactor = currentFactor > 1 ? 1 : currentFactor; + final num maxZoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; + if (currentZoomFactor < maxZoomFactor) { + axisRenderer._zoomFactor = maxZoomFactor; + axisRenderer._zoomPosition = 0.0; + currentZoomFactor = maxZoomFactor; + } onPinch(axisRenderer, currentZoomPosition, currentZoomFactor); } if (chart.onZooming != null) { @@ -758,7 +764,7 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { _chartState._zoomProgress = true; _calculateZoomAxesRange(_chart); _isPanning = _chart.zoomPanBehavior.enablePanning; - ZoomPanArgs zoomStartArgs; + ZoomPanArgs zoomStartArgs, zoomResetArgs; for (int axisIndex = 0; axisIndex < _chartState._chartAxis._axisRenderersCollection.length; axisIndex++) { @@ -801,10 +807,22 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { axisRenderer._zoomFactor = zoomFactor; axisRenderer._bounds = const Rect.fromLTWH(0, 0, 0, 0); axisRenderer._visibleLabels = []; + final num maxZoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; + if (zoomFactor < maxZoomFactor) { + axisRenderer._zoomFactor = maxZoomFactor; + axisRenderer._zoomPosition = 0.0; + zoomFactor = maxZoomFactor; + } if (_chart.onZoomEnd != null) { ZoomPanArgs zoomEndArgs; _bindZoomEvent(_chart, axisRenderer, zoomEndArgs, _chart.onZoomEnd); } + if (axisRenderer._zoomFactor.toInt() == 1 && + axisRenderer._zoomPosition.toInt() == 0 && + _chart.onZoomReset != null) { + _bindZoomEvent( + _chart, axisRenderer, zoomResetArgs, _chart.onZoomReset); + } } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart index f03fd7890..82676ec37 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart @@ -128,15 +128,13 @@ void _drawDashedPath(Canvas canvas, _CustomPaintStyle style, Offset moveToPoint, even = true; } } - if (even == false && !kIsWeb) { + if (even == false) { canvas.drawPath( _dashPath( path, dashArray: _CircularIntervalList(dashArray), ), paint); - } else { - canvas.drawPath(path, paint); } } else { canvas.drawPath(path, paint); @@ -165,7 +163,7 @@ num _calculateLogBaseValue(num value, num base) => /// To check if value is within range bool _withInRange(num value, _VisibleRange range) => - (value <= range.maximum) && (value >= range.minimum); + 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. @@ -1581,9 +1579,12 @@ dynamic _getInteractiveTooltipLabel( final ChartAxis axis = axisRenderer._axis; if (axisRenderer is CategoryAxisRenderer) { value = value < 0 ? 0 : value; - value = axisRenderer._labels[ - (value.round() >= axisRenderer._labels.length ? value - 1 : value) - .round()]; + value = axisRenderer._labels[(value.round() >= axisRenderer._labels.length + ? (value.round() > axisRenderer._labels.length + ? axisRenderer._labels.length - 1 + : value - 1) + : value) + .round()]; } else if (axisRenderer is DateTimeAxisRenderer) { final DateTimeAxis _dateTimeAxis = axisRenderer._axis; final DateFormat dateFormat = @@ -1605,7 +1606,11 @@ Path _getMarkerShapesPath(DataMarkerType markerType, Offset position, Size size, if (seriesRenderer._chart?.onMarkerRender != null && !seriesRenderer._isMarkerRenderEvent) { final MarkerRenderArgs event = _triggerMarkerRenderEvent( - seriesRenderer, size, markerType, index, animationController); + seriesRenderer, + size, + markerType, + seriesRenderer._dataPoints[index].visiblePointIndex, + animationController); markerType = event?.shape; size = Size(event.markerHeight, event.markerWidth); } @@ -2062,6 +2067,10 @@ void _stackedAreaPainter( rect); _path.moveTo(point1.x, point1.y); _strokePath.moveTo(point1.x, point1.y); + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < seriesRenderer._dataPoints.length; pointIndex++) { @@ -2225,6 +2234,10 @@ void _stackedRectPainter(Canvas canvas, dynamic seriesRenderer, seriesRenderer._yAxisRenderer?._axis?.plotOffset)); canvas.clipRect(axisClipRect); int segmentIndex = -1; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < seriesRenderer._dataPoints.length; pointIndex++) { @@ -2305,6 +2318,10 @@ void _stackedLinePainter( int segmentIndex = -1; double currentCummulativePos, nextCummulativePos; CartesianChartPoint startPoint, endPoint, currentPoint, _nextPoint; + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints = >[]; + } for (int pointIndex = 0; pointIndex < seriesRenderer._dataPoints.length; pointIndex++) { @@ -2560,15 +2577,19 @@ MarkerRenderArgs _triggerMarkerRenderEvent( CartesianSeriesRenderer seriesRenderer, Size size, DataMarkerType markerType, - int index, + int pointIndex, Animation animationController) { MarkerRenderArgs markerargs; + final num seriesIndex = seriesRenderer + ._chartState._chartSeries.visibleSeriesRenderers + .indexOf(seriesRenderer); final XyDataSeries series = seriesRenderer._series; final MarkerSettingsRenderer markerSettingsRenderer = seriesRenderer._markerSettingsRenderer; markerSettingsRenderer._color = series.markerSettings.color; if (seriesRenderer._chart.onMarkerRender != null) { - markerargs = MarkerRenderArgs(index); + markerargs = MarkerRenderArgs(pointIndex, seriesIndex, + seriesRenderer._visibleDataPoints[pointIndex].overallDataPointIndex); markerargs.markerHeight = size.height; markerargs.markerWidth = size.width; markerargs.shape = markerType; @@ -3229,3 +3250,111 @@ num _getCrossesAtValue( } return crossesAt; } + +List _getTooltipPaddingData(CartesianSeriesRenderer seriesRenderer, + bool isTrendLine, Rect region, Rect paddedRegion, Offset tooltipPosition) { + Offset padding, position; + if (seriesRenderer._seriesType == 'bubble' && !isTrendLine) { + padding = Offset(region.center.dx - region.centerLeft.dx, + 2 * (region.center.dy - region.topCenter.dy)); + position = Offset(tooltipPosition.dx, paddedRegion.top); + } else if (seriesRenderer._seriesType == 'scatter') { + padding = Offset(seriesRenderer._series.markerSettings.width, + seriesRenderer._series.markerSettings.height / 2); + position = Offset(tooltipPosition.dx, tooltipPosition.dy); + } else if (seriesRenderer._seriesType.contains('rangearea')) { + padding = Offset(seriesRenderer._series.markerSettings.width, + seriesRenderer._series.markerSettings.height / 2); + position = Offset(tooltipPosition.dx, tooltipPosition.dy); + } else { + padding = (seriesRenderer._series.markerSettings.isVisible) + ? Offset( + seriesRenderer._series.markerSettings.width / 2, + seriesRenderer._series.markerSettings.height / 2 + + seriesRenderer._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( + SfCartesianChartState chartState, + CartesianSeriesRenderer seriesRenderer, + int seriesIndex, + List oldSeriesRenderers) { + if (chartState._widgetNeedUpdate && + seriesRenderer._xAxisRenderer._zoomFactor == 1 && + seriesRenderer._yAxisRenderer._zoomFactor == 1 && + oldSeriesRenderers != null && + oldSeriesRenderers.isNotEmpty && + oldSeriesRenderers.length - 1 >= seriesIndex && + oldSeriesRenderers[seriesIndex]._seriesName == + seriesRenderer._seriesName) { + return oldSeriesRenderers[seriesIndex]; + } else { + return null; + } +} + +//Returns the old chart point for the given point and series index if present. +CartesianChartPoint _getOldChartPoint( + SfCartesianChartState chartState, + CartesianSeriesRenderer seriesRenderer, + Type segmentType, + int seriesIndex, + int pointIndex, + CartesianSeriesRenderer oldSeriesRenderer, + List oldSeriesRenderers) { + return !seriesRenderer._reAnimate && + (seriesRenderer._series.animationDuration > 0 && + chartState._widgetNeedUpdate && + !chartState._isLegendToggled && + oldSeriesRenderers != null && + oldSeriesRenderers.isNotEmpty && + oldSeriesRenderer != null && + oldSeriesRenderer._segments.isNotEmpty && + oldSeriesRenderer._segments[0].runtimeType == segmentType && + oldSeriesRenderers.length - 1 >= seriesIndex && + oldSeriesRenderer._dataPoints.length - 1 >= pointIndex) + ? oldSeriesRenderer._dataPoints[pointIndex] + : null; +} + +/// To trim the specific label text +String _trimAxisLabelsText(String text, num labelsExtent, TextStyle labelStyle, + ChartAxisRenderer axisRenderer) { + String label = text; + num size = _measureText( + text, axisRenderer._axis.labelStyle, axisRenderer._labelRotation) + .width; + if (size > labelsExtent) { + final int textLength = text.length; + for (int i = textLength - 1; i >= 0; --i) { + label = text.substring(0, i) + '...'; + size = _measureText(label, labelStyle, axisRenderer._labelRotation).width; + if (size <= labelsExtent) { + return label == '...' ? '' : label; + } + } + } + return label == '...' ? '' : label; +} + +/// Boolean to check whether it is necessary to render the axis tooltip. +bool _shouldShowAxisTooltip(SfCartesianChartState chartState) { + bool requireAxisTooltip = false; + for (int i = 0; + i < chartState._chartAxis._axisRenderersCollection.length; + i++) { + requireAxisTooltip = chartState._chartAxis._axisRenderersCollection[i]._axis + .maximumLabelWidth != + null || + chartState._chartAxis._axisRenderersCollection[i]._axis.labelsExtent != + null; + if (requireAxisTooltip) { + break; + } + } + return requireAxisTooltip; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_base.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_base.dart index a62674687..432403aed 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_base.dart @@ -654,7 +654,7 @@ class SfCircularChartState extends State : _deviceOrientation; _deviceOrientation = MediaQuery.of(context).orientation; return RepaintBoundary( - child: Container( + child: _ChartContainer( child: GestureDetector( child: Container( decoration: BoxDecoration( @@ -805,7 +805,7 @@ class SfCircularChartState extends State _prevSeriesRenderer._series = oldWidget.series[0]; _prevSeriesRenderer._oldRenderPoints = >[] //ignore: prefer_spread_collections - ..addAll(_prevSeriesRenderer._renderPoints); + ..addAll(_prevSeriesRenderer._renderPoints ?? []); _prevSeriesRenderer._renderPoints = >[]; } seriesRenderer._series = series; @@ -914,24 +914,21 @@ class _CircularArea extends StatelessWidget { onExit: (PointerEvent event) { chartState._tooltipBehaviorRenderer._isHovering = false; }, - child: Stack(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, - child: Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - decoration: - const BoxDecoration(color: Colors.transparent), - )), - ) - ])), + child: Listener( + onPointerUp: (PointerUpEvent event) => _onTapUp(event), + onPointerDown: (PointerDownEvent event) => _onTapDown(event), + onPointerMove: (PointerMoveEvent event) => + _performPointerMove(event), + child: GestureDetector( + onLongPress: _onLongPress, + onDoubleTap: _onDoubleTap, + child: Container( + height: constraints.maxHeight, + width: constraints.maxWidth, + child: _initializeChart(constraints, context), + decoration: const BoxDecoration(color: Colors.transparent), + )), + )), ); }); } @@ -939,8 +936,11 @@ class _CircularArea extends StatelessWidget { /// Find point index for selection void _calculatePointSeriesIndex(SfCircularChart chart, _Region pointRegion) { PointTapArgs pointTapArgs; - pointTapArgs = PointTapArgs(pointRegion.seriesIndex, pointRegion.pointIndex, - chartState._chartSeries.visibleSeriesRenderers[0]._dataPoints); + pointTapArgs = PointTapArgs( + pointRegion.seriesIndex, + pointRegion.pointIndex, + chartState._chartSeries.visibleSeriesRenderers[0]._dataPoints, + pointRegion.pointIndex); chart.onPointTapped(pointTapArgs); } @@ -958,15 +958,6 @@ class _CircularArea extends StatelessWidget { chartState._tapPosition = renderBox.globalToLocal(event.position); pointRegion = _getCircularPointRegion(chart, chartState._tapPosition, chartState._chartSeries.visibleSeriesRenderers[0]); - final CircularSeriesRenderer seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[0]; - if (chart.onPointTapped != null && pointRegion != null) { - _calculatePointSeriesIndex(chart, pointRegion); - } - if (chart.onDataLabelTapped != null) { - _triggerCircularDataLabelEvent( - chart, seriesRenderer, chartState, chartState._tapPosition); - } doubleTapPosition = chartState._tapPosition; if (chartState._tapPosition != null && pointRegion != null) { chartState._currentActive = _ChartInteraction( @@ -1026,6 +1017,7 @@ class _CircularArea extends StatelessWidget { chartState._chartSeries ._seriesPointSelection(pointRegion, ActivationMode.doubleTap); if (chart.tooltipBehavior.enable && + chartState._animateCompleted && chart.tooltipBehavior.activationMode == ActivationMode.doubleTap) { if (chart.tooltipBehavior.builder != null) { _showCircularTooltipTemplate(); @@ -1060,6 +1052,7 @@ class _CircularArea extends StatelessWidget { } } if (chart.tooltipBehavior.enable && + chartState._animateCompleted && chart.tooltipBehavior.activationMode == ActivationMode.longPress) { if (chart.tooltipBehavior.builder != null) { _showCircularTooltipTemplate(); @@ -1076,6 +1069,15 @@ class _CircularArea extends StatelessWidget { void _onTapUp(PointerUpEvent event) { chartState._tooltipBehaviorRenderer._isHovering = false; ChartTouchInteractionArgs touchArgs; + final CircularSeriesRenderer seriesRenderer = + chartState._chartSeries.visibleSeriesRenderers[0]; + if (chart.onPointTapped != null && pointRegion != null) { + _calculatePointSeriesIndex(chart, pointRegion); + } + if (chart.onDataLabelTapped != null) { + _triggerCircularDataLabelEvent( + chart, seriesRenderer, chartState, chartState._tapPosition); + } if (chartState._tapPosition != null) { if (chartState._currentActive != null && chartState._currentActive.series != null && @@ -1091,6 +1093,7 @@ class _CircularArea extends StatelessWidget { chartState._currentActive.region, ActivationMode.singleTap); } if (chart.tooltipBehavior.enable && + chartState._animateCompleted && chart.tooltipBehavior.activationMode == ActivationMode.singleTap && chartState._currentActive != null && chartState._currentActive.series != null) { @@ -1120,9 +1123,6 @@ class _CircularArea extends StatelessWidget { chartState._chartSeries.visibleSeriesRenderers[0]); final CircularSeriesRenderer seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[0]; - if (chart.onPointTapped != null && pointRegion != null) { - _calculatePointSeriesIndex(chart, pointRegion); - } if (chart.onDataLabelTapped != null) { _triggerCircularDataLabelEvent( chart, seriesRenderer, chartState, chartState._tapPosition); @@ -1131,8 +1131,8 @@ class _CircularArea extends StatelessWidget { chartState._currentActive = _ChartInteraction( pointRegion.seriesIndex, pointRegion.pointIndex, - chartState - ._chartSeries.visibleSeriesRenderers[pointRegion.seriesIndex], + chartState._chartSeries + .visibleSeriesRenderers[pointRegion.seriesIndex]._series, chartState ._chartSeries .visibleSeriesRenderers[pointRegion.seriesIndex] @@ -1149,7 +1149,8 @@ class _CircularArea extends StatelessWidget { chartState._currentActive != null && chartState._currentActive.series != null) { chartState._tooltipBehaviorRenderer._isHovering = true; - if (chart.tooltipBehavior.builder != null) { + if (chart.tooltipBehavior.builder != null && + chartState._animateCompleted) { _showCircularTooltipTemplate(); } else { final Offset position = renderBox.globalToLocal(event.position); @@ -1362,19 +1363,24 @@ class _CircularArea extends StatelessWidget { void _bindTooltipWidgets(BoxConstraints constraints) { chart.tooltipBehavior._chartState = chartState; if (chart.tooltipBehavior.enable) { + final List tooltipWidgets = []; if (chart.tooltipBehavior.builder != null) { chartState._tooltipBehaviorRenderer._tooltipTemplate = _TooltipTemplate( show: false, clipRect: chartState._chartContainerRect, - duration: chart.tooltipBehavior.duration); - chartState._chartWidgets + tooltipBehavior: chart.tooltipBehavior, + duration: chart.tooltipBehavior.duration, + chartState: chartState); + tooltipWidgets .add(chartState._tooltipBehaviorRenderer._tooltipTemplate); } else { chartState._tooltipBehaviorRenderer._chartTooltip = _ChartTooltipRenderer(chartState: chartState); - chartState._chartWidgets - .add(chartState._tooltipBehaviorRenderer._chartTooltip); + tooltipWidgets.add(chartState._tooltipBehaviorRenderer._chartTooltip); } + final Widget uiWidget = + IgnorePointer(ignoring: true, child: Stack(children: tooltipWidgets)); + chartState._chartWidgets.add(uiWidget); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/series_base.dart index 624a3ea26..0cde9f029 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/series_base.dart @@ -425,8 +425,20 @@ class _CircularSeries { final SfCircularChartState chartState = _chartState; final CircularSeriesRenderer seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; + int currentSelectedIndex; if (seriesRenderer._isSelectionEnable && mode == chart.selectionGesture) { if (chartState._selectionData.isNotEmpty) { + if (!chart.enableMultiSelection && + chartState._selectionData.isNotEmpty && + chartState._selectionData.length > 1) { + if (chartState._selectionData.contains(pointIndex)) { + currentSelectedIndex = pointIndex; + } + chartState._selectionData.clear(); + if (currentSelectedIndex != null) { + chartState._selectionData.add(pointIndex); + } + } for (int i = 0; i < chartState._selectionData.length; i++) { final int selectionIndex = chartState._selectionData[i]; if (!chart.enableMultiSelection) { diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series.dart index 80ac6bad5..75f9d418a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series.dart @@ -1226,10 +1226,9 @@ class CircularSeriesRenderer extends ChartSeriesRenderer { if (_chartState._selectionData.isNotEmpty) { for (int i = 0; i < _chartState._selectionData.length; i++) { final int selectionIndex = _chartState._selectionData[i]; - if (chart.onSelectionChanged != null && - selectionIndex == currentPointIndex) { + if (chart.onSelectionChanged != null) { chart.onSelectionChanged(_getSelectionEventArgs( - seriesRenderer, seriesIndex, currentPointIndex)); + seriesRenderer, seriesIndex, selectionIndex)); } if (currentPointIndex == selectionIndex) { pointStyle = _StyleOptions( diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/data_label_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/data_label_renderer.dart index db892056a..e25e78d0e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/data_label_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/data_label_renderer.dart @@ -150,16 +150,19 @@ void _renderCircularDataLabel( seriesRenderer._series.dataLabelSettings.color; if (chart.onDataLabelRender != null && !seriesRenderer._renderPoints[pointIndex].labelRenderEvent) { - seriesRenderer._renderPoints[pointIndex].labelRenderEvent = true; - dataLabelArgs = DataLabelRenderArgs( - seriesRenderer, seriesRenderer._renderPoints, pointIndex); + dataLabelArgs = DataLabelRenderArgs(seriesRenderer, + seriesRenderer._renderPoints, pointIndex, pointIndex); dataLabelArgs.text = label; dataLabelArgs.textStyle = dataLabelStyle; dataLabelArgs.color = dataLabelSettingsRenderer._color; chart.onDataLabelRender(dataLabelArgs); - label = dataLabelArgs.text; + label = point.text = dataLabelArgs.text; dataLabelStyle = dataLabelArgs.textStyle; + pointIndex = dataLabelArgs.pointIndex; dataLabelSettingsRenderer._color = dataLabelArgs.color; + if (animation.status == AnimationStatus.completed) { + seriesRenderer._dataPoints[pointIndex].labelRenderEvent = true; + } } final Size textSize = _measureText(label, dataLabelStyle); @@ -611,10 +614,13 @@ void _triggerCircularDataLabelEvent( CircularSeriesRenderer seriesRenderer, SfCircularChartState chartState, Offset position) { + final int seriesIndex = 0; final DataLabelSettings dataLabel = seriesRenderer._series.dataLabelSettings; for (int index = 0; index < seriesRenderer._dataPoints.length; index++) { final ChartPoint point = seriesRenderer._dataPoints[index]; - if (seriesRenderer._dataPoints[index].labelRect.contains(position)) { + if (dataLabel.isVisible && + seriesRenderer._dataPoints[index].labelRect != null && + seriesRenderer._dataPoints[index].labelRect.contains(position)) { if (dataLabel.labelPosition == ChartDataLabelPosition.inside) { final Offset labelLocation = _degreeToPoint(point.midAngle, (point.innerRadius + point.outerRadius) / 2, point.center); @@ -628,7 +634,7 @@ void _triggerCircularDataLabelEvent( } if (chart.onDataLabelTapped != null) { _dataLabelTapEvent(chart, seriesRenderer._series.dataLabelSettings, - index, point, position); + index, point, position, seriesIndex); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/helper.dart index ece4f96ad..d14fc8748 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/helper.dart @@ -323,3 +323,23 @@ num _findAngleDeviation(num innerRadius, num outerRadius, num totalAngle) { return (deviation * 360) / 100; } + +/// It returns the actual label value for tooltip and data label etc +dynamic _getDecimalLabelValue(dynamic value, [int showDigits]) { + if (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; + } + final dynamic text = value; + return text.toString(); +} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/common.dart b/packages/syncfusion_flutter_charts/lib/src/common/common.dart index 08a0814d5..4ab087efe 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/common.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/common.dart @@ -973,6 +973,9 @@ typedef ChartValueMapper = R Function(T datum, int index); typedef ChartWidgetBuilder = Widget Function(dynamic data, dynamic point, dynamic series, int pointIndex, int seriesIndex); +typedef ChartTrackballBuilder = Widget Function( + BuildContext context, TrackballDetails trackballDetails); + // Custom renderer for series typedef ChartSeriesRendererFactory = ChartSeriesRenderer Function( ChartSeries series); diff --git a/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart b/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart index a532e15c9..5dbd72ec2 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart @@ -6,34 +6,41 @@ part of charts; /// 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.pointIndex]); + TooltipArgs( + [this.seriesIndex, + this.dataPoints, + this.viewportPointIndex, + this.pointIndex]); - ///Tooltip text + /// Get and set the tooltip text. String text; - /// Header text of tooltip + /// Get and set the header text of the tooltip. String header; - /// x location + /// Get and set the x location of the tooltip. double locationX; - /// y location + /// Get and set the y location of the tooltip. double locationY; - /// Index of current series + /// Get the index of the current series. final dynamic seriesIndex; - /// List of datapoints in the series + /// Get the list of data points in the series. final List dataPoints; - /// Index of the current point + /// Get the overall index value of the tooltip. final num pointIndex; + + /// Get the viewport index value of the tooltip. + final num viewportPointIndex; } /// Holds the onActualRangeChanged event arguments. /// -/// ActualRangeChangedArgs is the type argument for onActualRangeChanged event. whenever the actual ranges is changed,onActualRangeChanged event is -/// triggered and provides options to set the actual minimum and maximum, visible minimum and maximum values. +/// 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 { @@ -46,38 +53,38 @@ class ActualRangeChangedArgs { this.actualInterval, this.orientation]); - /// Specify the name of the axis. + /// Get the name of the axis. final String axisName; - /// Specify the axis type . + /// Get the axis type. final ChartAxis axis; - /// Specify the minimum actual range to an axis. + /// Get the actual minimum range for an axis. final dynamic actualMin; - /// Specify the maximum actual range to an axis. + /// Get the actual maximum range for an axis. final dynamic actualMax; - /// Specify the actual interval of an axis. + /// Get the actual interval for an axis. final dynamic actualInterval; - /// Minimum visible range of the axis. + /// Get and set the minimum visible range for an axis. dynamic visibleMin; - /// Maximum visible range of the axis. + /// Get and set the maximum visible range for an axis. dynamic visibleMax; - /// Interval for the visible range of the axis. + /// Get and set the interval for the visible range of an axis. dynamic visibleInterval; - /// Specifies the axis orientation. + /// Get the orientation for an axis. final AxisOrientation orientation; } /// Holds the onAxisLabelRender event arguments. /// -/// AxisLabelRenderArgs is the type argument for onAxisLabelRender event. whenever the Axis gets rednered,onAxisLabelRender event is -/// triggered and provides options to set the axis label text, axis name, label text style, orientation, and axis type. +/// AxisLabelRenderArgs is the type argument for onAxisLabelRender event. Whenever the axis gets rendered, the onAxisLabelRender event is +/// triggered and provides options to set the axis label text and label text style. /// ///It has the public properties of axis label text, axis name, axis type, label text style, and orientation. @@ -85,22 +92,25 @@ class AxisLabelRenderArgs { /// Creating an argument constructor of AxisLabelRenderArgs class. AxisLabelRenderArgs([this.value, this.axisName, this.orientation, this.axis]); - /// Text value of the axis label. + /// Get and set the text value of the axis label. String text; - /// Value of the axis label. + /// Trimmed text value of the axis label. + // String _trimmedText; + + /// Get the value of the axis label. final num value; - /// Specifies the axis name. + /// Get the axis name. final String axisName; - /// Specifies the axis orientation. + /// Get the orientation for an axis. final AxisOrientation orientation; - /// Chart axis type and its property. + /// Get the chart axis type and its properties. final ChartAxis axis; - /// Text style of the axis label. + /// Get and set the text style of an axis label. TextStyle textStyle = const TextStyle( fontFamily: 'Roboto', fontStyle: FontStyle.normal, @@ -110,68 +120,101 @@ class AxisLabelRenderArgs { /// Holds the onDataLabelRender event arguments. /// -/// DataLabelRenderArgs is the type of Argument to the onDataLabelRender event, whenever the datalabel gets rendered, onDataLabelRender event is -/// triggered it provides options to customize the data label text, data label text style, series, data point, and current point index value. +/// 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. +/// 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.pointIndex]); + DataLabelRenderArgs( + [this.seriesRenderer, + this.dataPoints, + this.viewportPointIndex, + this.pointIndex]); - /// Text value of the data label. + /// Get and set the text value of a data label. String text; - /// Style property of the data label 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); - /// Current series. + /// 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; - /// Data points of the series. + /// Get the data points of the series. final dynamic dataPoints; - ///Current point index . + /// Get the overall index value of a data label. final num pointIndex; - /// Color of datalabels + /// 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 viewport index value of a data label. + final num viewportPointIndex; } /// Holds the onLegendItemRender event arguments. /// -/// Triggers when the legend item is rendering.It can be customize the [text], [legendIconType], -/// [seriesIndex], [pointIndex] and [color]. +/// The onLegendItemRender event triggers when the legend item is rendering and can customize the [text], [legendIconType], and [color]. /// -/// _Note:_ [pointIndex] and [color] only applicable for [SfCircularChart]. +/// _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]); - ///Legend text. + /// Get and set the legend text. String text; - ///Specifies the shape of legend. + /// Get and set the shape of a legend. LegendIconType legendIconType; - ///Current series index. + /// Get the current series index. final int seriesIndex; - ///Current point index. + /// Get the current point index. final int pointIndex; - ///Color of legend + /// Get and set the color of the legend icon. Color color; } ///Holds the arguments for the event onTrendlineRender. /// -/// Event is triggered when the trend line is rendered, trendline arguments such as [opacity], [color] and -/// [dashArray] can be customized. +/// Event is triggered when the trend line is rendered, trendline arguments such as [opacity], [color], and [dashArray] can be customized. class TrendlineRenderArgs { /// Creating an argument constructor of TrendlineRenderArgs class. TrendlineRenderArgs( @@ -182,86 +225,85 @@ class TrendlineRenderArgs { this.seriesName, this.data]); - /// Intercept value + /// Get the intercept value. final double intercept; - ///Index of trendline + /// Get the index of the trendline. final int trendlineIndex; - ///Index of series + /// Get the index of the series. final int seriesIndex; - /// Name of the trendline + /// Get the name of the trendline. final String trendlineName; - /// Name of the series + /// Get the name of the series. final String seriesName; - ///Event color + /// Get and set the color of the trendline. Color color; - ///Opacity value + /// Get and set the opacity value. double opacity; - /// TrendlineRenderArgs dashArray + /// Get and set the dash array value of a trendline. List dashArray; - /// Data points of the Trendline. + /// Get the data points of the trendline. final List> data; } -///Holds arguments for TrackballPositionChanging event. +/// Holds arguments for onTrackballPositionChanging event. /// -///The event is triggered when the trackball is rendered and the trackball arguments can be customized. -///Provides options to customise the label text. +/// The event is triggered when the trackball is rendered and provides options to customize the label text. class TrackballArgs { - /// Chart point info + /// 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, -/// onCrosshairPositionChanging event is triggered, provids options to customize the text, Line color, axis name, and orientation and value. +/// 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 name, and orientation and value. +/// 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]); - /// Specifies the type of chart axis and its property. + /// Get the type of chart axis and its properties. final ChartAxis axis; - /// Specifies the crosshair text. + /// Get and set the crosshair tooltip text. String text; - /// Specifies the color of the cross-hair. + /// Get and set the color of the crosshair line. Color lineColor; - /// Visible range value. + /// Get the visible range value. final dynamic value; - /// Name of the axis. + /// Get the name of the axis. final String axisName; - /// Specifies the axis orientation. + /// 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. +/// 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 { - /// Position of the Touch interaction. + /// Get the position of the touch interaction. Offset position; } /// Holds the zooming event arguments. /// -/// In this ZoomPanArg events will trigger onZooming, onZoomStart, onZoomEnd and onZoomReset. +/// The zooming events are onZooming, onZoomStart, onZoomEnd and onZoomReset. /// It contains [axis], [currentZoomPosition], [currentZoomFactor], [previousZoomPosition] /// and [previousZoomFactor] arguments. /// @@ -270,132 +312,154 @@ class ZoomPanArgs { /// Creating an argument constructor of ZoomPanArgs class. ZoomPanArgs([this.axis, this.previousZoomPosition, this.previousZoomFactor]); - ///Chart Axis types and property + /// Get the chart axis types and properties. final ChartAxis axis; - /// Position of current zooom. + /// Get and set the current zoom position. double currentZoomPosition; - /// Zoom factor for currrent position. + /// Get and set the current zoom factor. double currentZoomFactor; - /// Previous zooom position. + /// Get the previous zooom position. final double previousZoomPosition; - /// Previous zoom factor. + /// Get the previous zoom factor. final double previousZoomFactor; } /// Holds the onPointTapped event arguments. /// -/// The PointTapArgs is the argument type of onPointTapped event, whenever the [onPointTapped] is triggered it sets the series index, -/// current point index, and respective data point. -/// -/// It has the public properties of the series index, point index, and data points. +/// The PointTapArgs is the argument type of onPointTapped event, whenever the [onPointTapped] is triggered, gets the series index, current point index, and the data points. /// class PointTapArgs { /// Creating an argument constructor of PointTapArgs class. - PointTapArgs([this.seriesIndex, this.pointIndex, this.dataPoints]); + PointTapArgs( + [this.seriesIndex, + this.viewportPointIndex, + this.dataPoints, + this.pointIndex]); - /// Series index value. + /// Get the series index. final int seriesIndex; - /// Current point index. + /// Get the overall index value. final int pointIndex; - /// Stores the list of data points. + /// Get the list of data points. final List dataPoints; + + /// Get the viewport index value. + final num viewportPointIndex; } /// Holds the onAxisLabelTapped event arguments. /// -/// This is the argument type of onAxisLabelTapped event. Whenever the Axis lebal is tapped, onAxisLabelTapped event is -/// triggered and provides options to set the axis type, label text, and axis name. +/// 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. /// -/// It provides options for axis type, axis label text, and axis name. class AxisLabelTapArgs { /// Creating an argument constructor of AxisLabelTapArgs class. AxisLabelTapArgs([this.axis, this.axisName]); - /// Specifies the type of chart axis and its property. + /// Get the type of chart axis and its properties. final ChartAxis axis; - /// Text of the axis label at the tap position. + /// Get the text of the axis label at the tapped position. String text; - /// The value holds the properties of the visible label. + /// Get the value holds the properties of the visible label. num value; - /// Specifies the axis name. + /// Get the axis name. final String axisName; } /// Holds the onLegendTapped event arguments. /// -/// When the legend is tapped, the legendtapArgs event is triggered. -/// It contains [series], [seriesIndex], [pointIndex] that can be customized. +/// 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]); - ///Specifies the current series. + /// 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; - ///Specifies the current series index. + /// Get the current series index. final int seriesIndex; - ///Specifies the current point index. + /// Get the current point index. final int pointIndex; } /// Holds the onSelectionChanged event arguments. /// -/// The selection arguments can be changed when the selection event is performed. +/// Here [selectedColor], [unselectedColor], [selectedBorderColor], [selectedBorderWidth], [unselectedBorderColor] and [unselectedBorderWidth] can be customized. /// -/// The selection arguments such as color, width can be customized. class SelectionArgs { /// Creating an argument constructor of SelectionArgs class. SelectionArgs( [this.seriesRenderer, this.seriesIndex, - this.pointIndex, - this.overallDataPointIndex]); + this.viewportPointIndex, + this.pointIndex]); - ///Selected series + /// Get the selected series. final dynamic seriesRenderer; - ///color of the selected series or data points + /// Get and set the color of the selected series or data points. Color selectedColor; - ///color of unselected series or data points + /// Get and set the color of unselected series or data points. Color unselectedColor; - ///border color of the selected series or data points + /// Get and set the border color of the selected series or data points. Color selectedBorderColor; - ///width of the selected series or data points + /// Get and set the border width of the selected series or data points. double selectedBorderWidth; - ///border color of the unselected series or data points + /// Get and set the border color of the unselected series or data points. Color unselectedBorderColor; - /// width of the unselected series or data points + /// Get and set the border width of the unselected series or data points. double unselectedBorderWidth; - /// series index of the series in the chart + /// Get the series index. final int seriesIndex; - /// point index of the points in the series + /// Get the overall index value of the selected data points. final int pointIndex; - /// data point index of points in the series - final int overallDataPointIndex; + /// Get the viewport index value of the selected data points. + final int viewportPointIndex; } /// Holds the onIndicatorRender event arguments. /// -///Triggers when indicator is rendering. You can customize the -///[indicatorname], [signalLineColor], [signalLineWidth], linedashArray and so on. +///Triggers when indicator is rendering. You can customize the [indicatorName], [signalLineColor], [signalLineWidth], and [lineDashArray]. /// /// _Note:_ This is only applicable for [SfCartesianChart]. class IndicatorRenderArgs { @@ -403,65 +467,68 @@ class IndicatorRenderArgs { IndicatorRenderArgs( [this.indicator, this.index, this.seriesName, this.dataPoints]); - ///Specifies which incicator type to use. + /// Get the technical indicator information. final TechnicalIndicators indicator; - ///Used to change the indicator name. - String indicatorname; + /// Get and set the indicator name. + String indicatorName; - /// Current index + /// Get the current index of the technical indicator. final int index; - ///Used to change the color of the signal line. + /// Get and set the color of the signal line. Color signalLineColor; - ///Used to change the width of the signal line. + /// Get and set the width of the signal line. double signalLineWidth; - ///Used to change the dash array size. + /// Get and set the dash array size. List lineDashArray; - ///Specifies the series name. + /// Get the series name. final String seriesName; - ///Specifies the current datapoints. + /// 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 point index, -/// series index shape of the marker, marker width, and height can be customized. +/// 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.pointIndex, this.seriesIndex]); + MarkerRenderArgs( + [this.viewportPointIndex, this.seriesIndex, this.pointIndex]); - /// Point index of the marker. + /// Get the overall index value of the marker. final int pointIndex; - /// Series index of the marker. + /// Get the series index of the marker. final int seriesIndex; - /// Stores the Shape of the marker. + /// Get and set the shape of the marker. DataMarkerType shape; - /// Stores the width of the marker. + /// Get and set the width of the marker. double markerWidth; - /// Stores the height of the marker. + /// Get and set the height of the marker. double markerHeight; - /// Stores the color of the marker. + /// Get and set the color of the marker. Color color; - /// Stores the border color of the marker. + /// Get and set the border color of the marker. Color borderColor; - /// border width of marker + /// Get and set the border width of marker. double borderWidth; + + /// Get the viewport index value of the marker. + final num viewportPointIndex; } ///Holds the onDataLabelTapped callback arguments. @@ -471,21 +538,24 @@ class MarkerRenderArgs { class DataLabelTapDetails { /// Creating an argument constructor of DataLabelTapDetails class. - DataLabelTapDetails( - this.seriesIndex, this.pointIndex, this.text, this.dataLabelSettings); + DataLabelTapDetails(this.seriesIndex, this.viewportPointIndex, this.text, + this.dataLabelSettings, this.pointIndex); - ///Position of the tapped data label in logical pixels. + /// Get the position of the tapped data label in logical pixels. Offset position; - ///Series index of the tapped data label. + /// Get the series index of the tapped data label. final int seriesIndex; - ///Point index of the tapped data label. + /// Get the overall index value of the tapped data label. final int pointIndex; - ///Text of the tapped data label. + /// Get the text of the tapped data label. final String text; - ///Data label customization options specified in that particular series. + /// Get the data label customization options specified in that particular series. final DataLabelSettings dataLabelSettings; + + /// Get the viewport index value of the tapped data label. + final int viewportPointIndex; } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/legend/legend.dart b/packages/syncfusion_flutter_charts/lib/src/common/legend/legend.dart index 381c4db79..d18d9c307 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/legend/legend.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/legend/legend.dart @@ -149,7 +149,9 @@ class _ChartLegend { legend.overflowMode == LegendItemOverflowMode.none) { if (maxLegendWidth + currentWidth <= maxRenderWidth) { legendWidth += currentWidth; - legendHeight = max(legendHeight, currentHeight); + legendHeight = currentHeight > maxRenderHeight + ? maxRenderHeight + : max(legendHeight, currentHeight); needRender = true; } else { needRender = false; @@ -172,7 +174,9 @@ class _ChartLegend { legend.overflowMode == LegendItemOverflowMode.none) { if (maxLegendHeight + currentHeight <= maxRenderHeight) { legendHeight += currentHeight; - legendWidth = max(legendWidth, currentWidth); + legendWidth = currentWidth > maxRenderWidth + ? maxRenderWidth + : max(legendWidth, currentWidth); needRender = true; } else { needRender = false; @@ -249,6 +253,28 @@ class _ChartLegend { if (!_chartState._legendToggleStates.contains(legendRenderContext)) { _chartState._legendToggleStates.add(legendRenderContext); } + } else if (_chartState._widgetNeedUpdate && + (seriesRenderer._oldSeries != null && + (series.isVisible && + !_chartState._legendToggling && + seriesRenderer._visible))) { + final List visibleSeriesRenderers = + _chartState._chartSeries.visibleSeriesRenderers; + final String legendItemText = + visibleSeriesRenderers[index]._series.legendItemText ?? + series.name ?? + 'Series $index'; + final int seriesIndex = visibleSeriesRenderers.indexOf(seriesRenderer); + final legendToggle = <_LegendRenderContext>[] + //ignore: prefer_spread_collections + ..addAll(_chartState._legendToggleStates); + for (final legendContext in _chartState._legendToggleStates) { + if (seriesIndex == legendContext.seriesIndex && + legendContext.text == legendItemText) { + legendToggle.remove(legendContext); + } + } + _chartState._legendToggleStates = legendToggle; } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart b/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart index 8ec3eac72..38df64c09 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart @@ -82,7 +82,7 @@ class _LegendRenderer with _CustomizeLegend { textStyle: legend.textStyle, fontColor: legendRenderer._renderer.getLegendTextColor( index, legendItem, chartState._chartTheme.legendTextColor)); - + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); _drawLegendShape( index, iconOffset, diff --git a/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart b/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart index d83271233..db5117d19 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart @@ -31,130 +31,86 @@ class _RenderTemplateState extends State<_RenderTemplate> super.initState(); } + /// To calculate need to measure template + void _templatesMeasureCompleted() { + final RenderBox renderBox = widget.template.context.findRenderObject(); + widget.template.size = renderBox.size; + if (mounted) { + setState(() { + widget.template.needMeasure = false; + }); + } + } + @override Widget build(BuildContext context) { - Widget currentWidget; - final List templateWidgets = []; - if (widget.template.needMeasure) { + num locationX, locationY; + final _ChartTemplateInfo templateInfo = widget.template; + Widget currentWidget = Container(); + Widget renderWidget; + if (templateInfo.needMeasure && templateInfo.templateType != 'DataLabel') { currentWidget = Opacity(opacity: 0.0, child: widget.template.widget); widget.template.context = context; SchedulerBinding.instance .addPostFrameCallback((_) => _templatesMeasureCompleted()); } else { - num locationX, locationY; - - /// Here we have added 5px padding to each templates - const int padding = 5; - final _ChartTemplateInfo templateInfo = widget.template; - locationX = templateInfo.location.dx - - (templateInfo.horizontalAlignment == ChartAlignment.near - ? 0 - : templateInfo.horizontalAlignment == ChartAlignment.center - ? templateInfo.size.width / 2 - : templateInfo.size.width); - locationY = templateInfo.location.dy - - (templateInfo.verticalAlignment == ChartAlignment.near - ? 0 - : templateInfo.verticalAlignment == ChartAlignment.center - ? templateInfo.templateType == 'DataLabel' - ? templateInfo.size.height + padding - : templateInfo.size.height / 2 - : templateInfo.size.height); - bool isLabelWithInRange = true; - if (templateInfo.templateType == 'DataLabel' && - widget.chartState is SfCartesianChartState) { - final dynamic seriesRenderer = widget.chartState._chartSeries - .visibleSeriesRenderers[templateInfo.seriesIndex]; - seriesRenderer._dataLabelSettingsRenderer = - DataLabelSettingsRenderer(seriesRenderer._series.dataLabelSettings); - final CartesianChartPoint point = - seriesRenderer._dataPoints != null - ? (seriesRenderer._dataPoints.isNotEmpty) - ? seriesRenderer._dataPoints[templateInfo.pointIndex] - : null - : null; - if (point != null && seriesRenderer._seriesType != 'boxandwhisker') { - if (point.region == null && - seriesRenderer._seriesType != 'waterfall') { - seriesRenderer._calculateRegionData( - widget.chartState, - seriesRenderer, - 0, - point, - templateInfo.pointIndex, - null, - null, - null, - null); - } - _calculateDataLabelPosition( - seriesRenderer, - point, - templateInfo.pointIndex, - widget.chartState, - seriesRenderer._dataLabelSettingsRenderer, - animationController, - widget.template.size, - templateInfo.location); - locationX = point.labelLocation.x; - locationY = point.labelLocation.y; - isLabelWithInRange = _isLabelWithinRange(seriesRenderer, point); - } - } - final Rect rect = Rect.fromLTWH(locationX, locationY, - templateInfo.size.width, templateInfo.size.height); - final bool isCollide = (templateInfo.templateType == 'DataLabel') - ? _findingCollision(rect, widget.chartState._dataLabelTemplateRegions) - : false; - if (!isCollide && - _isTemplateWithinBounds(templateInfo.clipRect, rect) && - isLabelWithInRange) { - final Widget renderWidget = Stack(children: [ + if (templateInfo.templateType == 'DataLabel') { + renderWidget = _ChartTemplateRenderObject( + child: templateInfo.widget, + templateInfo: templateInfo, + chartState: widget.chartState, + animationController: animationController); + } else { + locationX = templateInfo.location.dx - + (templateInfo.horizontalAlignment == ChartAlignment.near + ? 0 + : templateInfo.horizontalAlignment == ChartAlignment.center + ? templateInfo.size.width / 2 + : templateInfo.size.width); + locationY = templateInfo.location.dy - + (templateInfo.verticalAlignment == ChartAlignment.near + ? 0 + : templateInfo.verticalAlignment == ChartAlignment.center + ? templateInfo.size.height / 2 + : templateInfo.size.height); + renderWidget = Stack(children: [ Positioned( left: locationX, top: locationY, child: templateInfo.widget, ) ]); - (templateInfo.templateType == 'DataLabel') - ? widget.chartState._dataLabelTemplateRegions.add(rect) - : widget.chartState._annotationRegions.add(rect); - - if (templateInfo.animationDuration > 0) { - final dynamic seriesRenderer = - (templateInfo.templateType == 'DataLabel') - ? widget.chartState._chartSeries - .visibleSeriesRenderers[templateInfo.seriesIndex] - : null; - final bool needsAnimate = widget.chartState._oldDeviceOrientation == - MediaQuery.of(context).orientation && - ((seriesRenderer != null && - seriesRenderer is CartesianSeriesRenderer) - ? seriesRenderer._needAnimateSeriesElements - : true); - animationController = AnimationController( - duration: - Duration(milliseconds: widget.template.animationDuration), - vsync: this); - animation = Tween(begin: 0.0, end: 1.0).animate( - CurvedAnimation( - parent: animationController, curve: Curves.linear)); - templateInfo.animationController = animationController; - animationController.forward(from: 0.0); - templateControllerList.add(animationController); - templateWidgets.add(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 { - templateWidgets.add(renderWidget); - } } - currentWidget = Container(child: Stack(children: templateWidgets)); + if (templateInfo.animationDuration > 0) { + final dynamic seriesRenderer = + (templateInfo.templateType == 'DataLabel') + ? widget.chartState._chartSeries + .visibleSeriesRenderers[templateInfo.seriesIndex] + : null; + final bool needsAnimate = widget.chartState._oldDeviceOrientation == + MediaQuery.of(context).orientation && + ((seriesRenderer != null && + seriesRenderer is CartesianSeriesRenderer) + ? seriesRenderer._needAnimateSeriesElements + : true); + animationController = AnimationController( + duration: Duration(milliseconds: widget.template.animationDuration), + vsync: this); + animation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: animationController, curve: Curves.linear)); + templateInfo.animationController = animationController; + animationController.forward(from: 1.0); + templateControllerList.add(animationController); + currentWidget = AnimatedBuilder( + animation: animationController, + child: renderWidget, + builder: (BuildContext context, Widget _widget) { + final double value = needsAnimate ? animationController.value : 1; + return Opacity(opacity: value * 1.0, child: _widget); + }); + } else { + currentWidget = renderWidget; + } } return currentWidget; } @@ -169,14 +125,157 @@ class _RenderTemplateState extends State<_RenderTemplate> } super.dispose(); } +} - /// To calculate need to measure template - void _templatesMeasureCompleted() { - final RenderBox renderBox = widget.template.context.findRenderObject(); - widget.template.size = renderBox.size; - setState(() { - widget.template.needMeasure = false; - }); +/// Represents the render object for annotation widget. +class _ChartTemplateRenderObject extends SingleChildRenderObjectWidget { + const _ChartTemplateRenderObject( + {Key key, + Widget child, + this.templateInfo, + this.chartState, + this.animationController}) + : super(key: key, child: child); + + final _ChartTemplateInfo templateInfo; + + final dynamic chartState; + + final AnimationController animationController; + + @override + RenderObject createRenderObject(BuildContext context) { + return _ChartTemplateRenderBox( + templateInfo, chartState, 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._chartState, this._animationController, + [RenderBox child]) + : super(child); + + _ChartTemplateInfo _templateInfo; + + final dynamic _chartState; + + 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; + + /// Here we have added 5px padding to each templates + const int padding = 5; + child.layout(constraints, parentUsesSize: true); + size = constraints.constrain(Size(child.size.width, child.size.height)); + if (child.parentData is BoxParentData) { + final BoxParentData childParentData = child.parentData; + 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 + ? _templateInfo.templateType == 'DataLabel' + ? child.size.height + padding + : child.size.height / 2 + : child.size.height); + if (_templateInfo.templateType == 'DataLabel' && + _chartState is SfCartesianChartState) { + final dynamic seriesRenderer = _chartState + ._chartSeries.visibleSeriesRenderers[_templateInfo.seriesIndex]; + seriesRenderer._chartState = _chartState; + seriesRenderer._dataLabelSettingsRenderer = DataLabelSettingsRenderer( + seriesRenderer._series.dataLabelSettings); + final CartesianChartPoint point = + seriesRenderer._dataPoints != null + ? (seriesRenderer._dataPoints.isNotEmpty) + ? seriesRenderer._dataPoints[_templateInfo.pointIndex] + : null + : null; + if (seriesRenderer._isRectSeries || + seriesRenderer._seriesType.contains('hilo') || + seriesRenderer._seriesType.contains('candle') || + seriesRenderer._seriesType.contains('box')) { + seriesRenderer.sideBySideInfo = + _calculateSideBySideInfo(seriesRenderer, _chartState); + } + seriesRenderer._hasDataLabelTemplate = true; + if (seriesRenderer._seriesType.contains('spline')) { + _calculateSplineAreaControlPoints(seriesRenderer); + } + if (point != null && seriesRenderer._seriesType != 'boxandwhisker') { + if (point.region == null && + seriesRenderer._seriesType != 'waterfall') { + if (seriesRenderer._visibleDataPoints == null || + seriesRenderer._visibleDataPoints.length >= + seriesRenderer._dataPoints.length) { + seriesRenderer._visibleDataPoints = + >[]; + } + seriesRenderer._calculateRegionData(_chartState, seriesRenderer, + 0, point, _templateInfo.pointIndex, null, null, null, null); + } + _calculateDataLabelPosition( + seriesRenderer, + point, + _templateInfo.pointIndex, + _chartState, + seriesRenderer._dataLabelSettingsRenderer, + _animationController, + size, + _templateInfo.location); + locationX = point.labelLocation.x; + locationY = point.labelLocation.y; + isLabelWithInRange = _isLabelWithinRange(seriesRenderer, point); + } + } + final Rect rect = + Rect.fromLTWH(locationX, locationY, size.width, size.height); + final bool isCollide = (_templateInfo.templateType == 'DataLabel') + ? _findingCollision(rect, _chartState._dataLabelTemplateRegions) + : false; + if (!isCollide && + _isTemplateWithinBounds(_templateInfo.clipRect, rect) && + isLabelWithInRange) { + (_templateInfo.templateType == 'DataLabel') + ? _chartState._dataLabelTemplateRegions.add(rect) + : _chartState._annotationRegions.add(rect); + childParentData.offset = Offset(locationX, locationY); + } else { + child.layout(constraints.copyWith(maxWidth: 0), parentUsesSize: true); + } + } + } else { + size = Size.zero; + } } /// To check template is within bounds @@ -236,9 +335,11 @@ class _ChartTemplateState extends State<_ChartTemplate> { } void templateRender() { - setState(() { - widget.render = true; - }); + if (mounted) { + setState(() { + widget.render = true; + }); + } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip.dart b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip.dart index dfb7d34ae..89d55d6d1 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip.dart @@ -369,7 +369,35 @@ class TooltipBehavior { final TooltipBehaviorRenderer tooltipBehaviorRenderer = _chartState._tooltipBehaviorRenderer; bool isInsidePointRegion; + String text = ''; + String trimmedText = ''; + Offset axisLabelPosition; if (chart is SfCartesianChart) { + _chartState._requireAxisTooltip = false; + for (int i = 0; + i < _chartState._chartAxis._axisRenderersCollection.length; + i++) { + final List labels = + _chartState._chartAxis._axisRenderersCollection[i]._visibleLabels; + for (int k = 0; k < labels.length; k++) { + if (_chartState + ._chartAxis._axisRenderersCollection[i]._axis.isVisible && + labels[k]._labelRegion != null && + labels[k]._labelRegion.contains(Offset(x, y))) { + _chartState._requireAxisTooltip = true; + text = labels[k].text; + trimmedText = labels[k].renderText ?? ''; + tooltipBehaviorRenderer._painter?.prevTooltipValue = + tooltipBehaviorRenderer._painter?.currentTooltipValue; + axisLabelPosition = labels[k]._labelRegion.center; + // -3 to indicte axis tooltip + tooltipBehaviorRenderer._painter?.currentTooltipValue = + TooltipValue(null, k, 0); + } + } + } + } + if (chart is SfCartesianChart && !_chartState._requireAxisTooltip) { for (int i = 0; i < _chartState._chartSeries.visibleSeriesRenderers.length; i++) { @@ -416,7 +444,23 @@ class TooltipBehavior { } } } - if (tooltipBehaviorRenderer._chartTooltip != null && + if (chart is SfCartesianChart && + activationMode != ActivationMode.none && + x != null && + y != null && + _chartState._requireAxisTooltip) { + final _ChartTooltipRendererState tooltipState = + tooltipBehaviorRenderer._chartTooltip?.state; + if (trimmedText.contains('...')) { + tooltipState?.show = true; + tooltipState?._needMarker = false; + tooltipBehaviorRenderer._painter + ._showAxisTooltip(axisLabelPosition, chart, text); + } else { + tooltipState.show = false; + hide(); + } + } else if (tooltipBehaviorRenderer._chartTooltip != null && activationMode != ActivationMode.none && x != null && y != null) { @@ -461,80 +505,87 @@ class TooltipBehavior { /// /// * yAxisName - name of the y axis the given point must be bind to. void show(dynamic x, double y, [String xAxisName, String yAxisName]) { - final dynamic chart = _chartState._chart; - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _chartState._tooltipBehaviorRenderer; - bool isInsidePointRegion = false; - ChartAxisRenderer xAxisRenderer, yAxisRenderer; - if (xAxisName != null && yAxisName != null) { - for (final ChartAxisRenderer axisRenderer - in _chartState._chartAxis._axisRenderersCollection) { - if (axisRenderer._name == xAxisName) { - xAxisRenderer = axisRenderer; - } else if (axisRenderer._name == yAxisName) { - yAxisRenderer = axisRenderer; + if (_chartState._chart is SfCartesianChart) { + final dynamic chart = _chartState._chart; + final TooltipBehaviorRenderer tooltipBehaviorRenderer = + _chartState._tooltipBehaviorRenderer; + bool isInsidePointRegion = false; + ChartAxisRenderer xAxisRenderer, yAxisRenderer; + if (xAxisName != null && yAxisName != null) { + for (final ChartAxisRenderer axisRenderer + in _chartState._chartAxis._axisRenderersCollection) { + if (axisRenderer._name == xAxisName) { + xAxisRenderer = axisRenderer; + } else if (axisRenderer._name == yAxisName) { + yAxisRenderer = axisRenderer; + } } + } else { + xAxisRenderer = _chartState._chartAxis._primaryXAxisRenderer; + yAxisRenderer = _chartState._chartAxis._primaryYAxisRenderer; } - } else { - xAxisRenderer = _chartState._chartAxis._primaryXAxisRenderer; - yAxisRenderer = _chartState._chartAxis._primaryYAxisRenderer; - } - final _ChartLocation position = _calculatePoint( - x is DateTime ? x.millisecondsSinceEpoch : x, - y, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - null, - _chartState._chartAxis._axisClipRect); - for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; - i++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; - if (seriesRenderer._visible && - seriesRenderer._series.enableTooltip && - seriesRenderer._regionalData != null) { - final double padding = (seriesRenderer._seriesType == 'bubble' || - seriesRenderer._seriesType == 'scatter' || - seriesRenderer._seriesType.contains('column') || - seriesRenderer._seriesType.contains('bar')) - ? 0 - : 15; // regional padding to detect smooth touch - seriesRenderer._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; - } - }); + final _ChartLocation position = _calculatePoint( + x is DateTime + ? x.millisecondsSinceEpoch + : (x is String && xAxisRenderer is CategoryAxisRenderer) + ? xAxisRenderer._labels.indexOf(x) + : x, + y, + xAxisRenderer, + yAxisRenderer, + _chartState._requireInvertedAxis, + null, + _chartState._chartAxis._axisClipRect); + for (int i = 0; + i < _chartState._chartSeries.visibleSeriesRenderers.length; + i++) { + final CartesianSeriesRenderer seriesRenderer = + _chartState._chartSeries.visibleSeriesRenderers[i]; + if (seriesRenderer._visible && + seriesRenderer._series.enableTooltip && + seriesRenderer._regionalData != null) { + final double padding = (seriesRenderer._seriesType == 'bubble' || + seriesRenderer._seriesType == 'scatter' || + seriesRenderer._seriesType.contains('column') || + seriesRenderer._seriesType.contains('bar')) + ? 0 + : 15; // regional padding to detect smooth touch + seriesRenderer._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 (_chartState._tooltipBehaviorRenderer._tooltipTemplate == null) { - final _ChartTooltipRendererState tooltipState = - tooltipBehaviorRenderer._chartTooltip?.state; - if (isInsidePointRegion ?? false) { - tooltipState?._needMarker = true; - tooltipState?._showTooltip(position.x, position.y); - } else { - //to show tooltip when the position is out of point region - tooltipState?.show = true; - tooltipState?._needMarker = false; - _chartState._tooltipBehaviorRenderer._painter._showChartAreaTooltip( - Offset(position.x, position.y), - xAxisRenderer, - yAxisRenderer, - chart); + if (_chartState._tooltipBehaviorRenderer._tooltipTemplate == null) { + final _ChartTooltipRendererState tooltipState = + tooltipBehaviorRenderer._chartTooltip?.state; + if (isInsidePointRegion ?? false) { + tooltipState?._needMarker = true; + tooltipState?._showTooltip(position.x, position.y); + } else { + //to show tooltip when the position is out of point region + tooltipState?.show = true; + tooltipState?._needMarker = false; + _chartState._tooltipBehaviorRenderer._painter._showChartAreaTooltip( + Offset(position.x, position.y), + xAxisRenderer, + yAxisRenderer, + chart); + } + } else if (_chartState._tooltipBehaviorRenderer._tooltipTemplate != + null && + position != null) { + tooltipBehaviorRenderer._showTemplateTooltip( + Offset(position.x, position.y), x, y); } - } else if (_chartState._tooltipBehaviorRenderer._tooltipTemplate != null && - position != null) { - tooltipBehaviorRenderer._showTemplateTooltip( - Offset(position.x, position.y), x, y); } } @@ -575,7 +626,12 @@ class TooltipBehavior { //to show the tooltip template when the provided indices are valid _chartState._circularArea ._showCircularTooltipTemplate(seriesIndex, pointIndex); - } else if (chart.tooltipBehavior.builder == null) { + } else if (chart.tooltipBehavior.builder == null && + chartState._animateCompleted && + pointIndex >= 0 && + (pointIndex + 1 <= + _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] + ._renderPoints.length)) { final ChartPoint chartPoint = _chartState._chartSeries .visibleSeriesRenderers[seriesIndex]._renderPoints[pointIndex]; if (chartPoint.isVisible) { @@ -595,15 +651,15 @@ class TooltipBehavior { //this shows the tooltip for triangular type of charts (funnerl and pyramid) if (chart.tooltipBehavior.builder == null) { _chartState._tooltipPointIndex = pointIndex; - final Offset position = - chart.series._renderPoints[pointIndex].region?.center; + final Offset position = _chartState._chartSeries + .visibleSeriesRenderers[0]._dataPoints[pointIndex].region?.center; x = position?.dx; y = position?.dy; tooltipBehaviorRenderer._chartTooltip?.state?._showTooltip(x, y); } else { - if (chart is SfFunnelChart) { + if (chart is SfFunnelChart && chartState._animateCompleted) { _chartState._funnelplotArea._showFunnelTooltipTemplate(pointIndex); - } else if (chart is SfPyramidChart) { + } else if (chart is SfPyramidChart && chartState._animateCompleted) { _chartState._chartPlotArea._showPyramidTooltipTemplate(pointIndex); } } @@ -657,10 +713,12 @@ class TooltipBehaviorRenderer with ChartBehavior { /// To show tooltip template void _showTemplateTooltip(Offset position, [dynamic xValue, dynamic yValue]) { final dynamic chart = _chartState._chart; - dynamic series; + dynamic series, currentSeriesRender; _tooltipTemplate?._alwaysShow = _tooltipBehavior.shouldAlwaysShow; - if (_chartState._chartAxis._axisClipRect.contains(position)) { + if (_chartState._chartAxis._axisClipRect.contains(position) && + _chartState._animateCompleted) { int seriesIndex, pointIndex; + int outlierIndex = -1; bool isTooltipRegion = false; if (!_isHovering) { //assingning null for the previous and current tooltip values in case of mouse not hovering @@ -675,8 +733,8 @@ class TooltipBehaviorRenderer with ChartBehavior { series = seriesRenderer._series; int j = 0; - final double padding = (series.runtimeType.toString().contains('Bar') || - series.runtimeType.toString().contains('Column')) || + final double padding = (seriesRenderer._seriesType.contains('bar') || + seriesRenderer._seriesType.contains('column')) || _isHovering ? 0 : 15; @@ -690,46 +748,90 @@ class TooltipBehaviorRenderer with ChartBehavior { 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); + Rect paddedRegion = Rect.fromLTRB(left, top, right, bottom); + final List outlierRegion = regionRect[5]; + final bool isTrendLine = values[values.length - 1].contains('true'); + + 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 = i; + currentSeriesRender = seriesRenderer; pointIndex = seriesRenderer._dataPoints.indexOf(regionRect[4]); _tooltipTemplate.state.seriesIndex = seriesIndex; - final Offset tooltipPosition = regionRect[1]; - _tooltipTemplate.rect = Rect.fromLTWH(tooltipPosition.dx, - tooltipPosition.dy, region.width, region.height); + Offset tooltipPosition = !(seriesRenderer._isRectSeries && + _tooltipBehavior.tooltipPosition != TooltipPosition.auto) + ? ((outlierIndex >= 0) + ? regionRect[6][outlierIndex] + : regionRect[1]) + : position; + final List paddingData = _getTooltipPaddingData( + seriesRenderer, + isTrendLine, + region, + paddedRegion, + tooltipPosition); + + tooltipPosition = paddingData[1] ?? tooltipPosition; + _tooltipTemplate.rect = Rect.fromLTWH( + tooltipPosition.dx, + tooltipPosition.dy - + (!(seriesRenderer._isRectSeries && + _tooltipBehavior.tooltipPosition != + TooltipPosition.auto) + ? paddingData[0].dy + : 0), + region.width, + region.height); _tooltipTemplate.template = chart.tooltipBehavior.builder( series.dataSource[j], regionRect[4], series, pointIndex, i); isTooltipRegion = true; } j++; }); - if (_chartState._tooltipBehaviorRenderer._isHovering && - isTooltipRegion) { - _tooltipTemplate.state.prevTooltipValue = - _tooltipTemplate.state.currentTooltipValue; - _tooltipTemplate.state.currentTooltipValue = - TooltipValue(seriesIndex, pointIndex); - } - _tooltipTemplate.show = isTooltipRegion; - final TooltipValue presentTooltip = - _tooltipTemplate.state.presentTooltipValue; - if (presentTooltip == null || - seriesIndex != presentTooltip.seriesIndex || - pointIndex != presentTooltip.pointIndex) { - //Current point is different than previous one so tooltip re-renders - _tooltipTemplate.state.presentTooltipValue = - TooltipValue(seriesIndex, pointIndex); - _tooltipTemplate?.state?._performTooltip(); - } else { - //Current point is same as previous one so timer is reset and tooltip is not re-rendered - _tooltipTemplate?.state?.tooltipTimer?.cancel(); - _tooltipTemplate?.state?.tooltipTimer = Timer( - Duration(milliseconds: _tooltipTemplate.duration.toInt()), - _tooltipTemplate.state.hideTooltipTemplate); - } } } + if (_chartState._tooltipBehaviorRenderer._isHovering && isTooltipRegion) { + _tooltipTemplate.state.prevTooltipValue = + _tooltipTemplate.state.currentTooltipValue; + _tooltipTemplate.state.currentTooltipValue = + TooltipValue(seriesIndex, pointIndex, outlierIndex); + } + _tooltipTemplate.show = isTooltipRegion; + final TooltipValue presentTooltip = + _tooltipTemplate.state.presentTooltipValue; + if (presentTooltip == null || + seriesIndex != presentTooltip.seriesIndex || + pointIndex != presentTooltip.pointIndex || + outlierIndex != presentTooltip.outlierIndex || + (currentSeriesRender != null && + currentSeriesRender._isRectSeries && + _tooltipBehavior.tooltipPosition != TooltipPosition.auto)) { + //Current point is different than previous one so tooltip re-renders + if (seriesIndex != null && pointIndex != null) { + _tooltipTemplate.state.presentTooltipValue = + TooltipValue(seriesIndex, pointIndex, outlierIndex); + } + _tooltipTemplate?.state?._performTooltip(); + } else { + //Current point is same as previous one so timer is reset and tooltip is not re-rendered + if (!_isHovering) { + _tooltipTemplate?.state?.tooltipTimer?.cancel(); + _tooltipTemplate?.state?.tooltipTimer = Timer( + Duration(milliseconds: _tooltipTemplate.duration.toInt()), + _tooltipTemplate.state.hideTooltipTemplate); + } + } + if (!isTooltipRegion && !_isInteraction && chart.series.isNotEmpty) { //to show tooltip temlate when the position resides outside point region final dynamic x = xValue ?? diff --git a/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart index 6675183db..1b45c415d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart @@ -7,10 +7,14 @@ typedef DataLabelTapCallback = void Function(DataLabelTapDetails onTapArgs); /// [onDataLabelTapped] event for all series. void _dataLabelTapEvent(dynamic chart, DataLabelSettings dataLabelSettings, - int pointIndex, dynamic point, Offset position) { + int pointIndex, dynamic point, Offset position, int seriesIndex) { DataLabelTapDetails datalabelArgs; - datalabelArgs = DataLabelTapDetails(0, pointIndex, - chart is SfCartesianChart ? point.label : point.text, dataLabelSettings); + 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; diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_base.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_base.dart index 8f45e3573..24e93ebe4 100644 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_base.dart @@ -446,6 +446,7 @@ class SfFunnelChartState extends State List _dataLabelTemplateRegions; List _selectionData; int _tooltipPointIndex; + //ignore: unused_field FunnelSeriesRenderer _seriesRenderer; Orientation _oldDeviceOrientation; Orientation _deviceOrientation; @@ -498,7 +499,7 @@ class SfFunnelChartState extends State : _deviceOrientation; _deviceOrientation = MediaQuery.of(context).orientation; return RepaintBoundary( - child: Container( + child: _ChartContainer( child: GestureDetector( child: Container( decoration: BoxDecoration( @@ -603,7 +604,7 @@ class SfFunnelChartState extends State _legendToggleStates = <_LegendRenderContext>[]; _legendToggleTemplateStates = <_MeasureWidgetContext>[]; _explodedPoints = []; - _animateCompleted = true; + _animateCompleted = false; _isLegendToggled = false; _widgetNeedUpdate = false; _dataLabelTemplateRegions = []; @@ -618,7 +619,11 @@ class SfFunnelChartState extends State // In this method, create and update the series renderer for each series // void _createAndUpdateSeriesRenderer([SfFunnelChart oldWidget]) { if (widget.series != null) { - final FunnelSeriesRenderer oldSeriesRenderer = _seriesRenderer; + final FunnelSeriesRenderer oldSeriesRenderer = + oldWidget != null && oldWidget.series != null + ? _chartSeries.visibleSeriesRenderers[0] + : null; + dynamic series; series = widget.series; @@ -841,8 +846,7 @@ class _FunnelPlotArea extends StatelessWidget { if (series.animationDuration > 0 && !chartState._didSizeChange && (chartState._oldDeviceOrientation == chartState._deviceOrientation) && - (chartState._initialRender || - chartState._widgetNeedUpdate || + ((!chartState._widgetNeedUpdate && chartState._initialRender) || chartState._isLegendToggled)) { chartState._animationController.duration = Duration(milliseconds: series.animationDuration.toInt()); @@ -886,12 +890,11 @@ class _FunnelPlotArea extends StatelessWidget { .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); chartState._renderDataLabel = _FunnelDataLabelRenderer( chartState: chartState, - show: series.animationDuration > 0 && - (chartState._deviceOrientation == - chartState._oldDeviceOrientation) && - !chartState._didSizeChange - ? false - : chartState._animateCompleted); + show: !chartState._widgetNeedUpdate + ? chartState._animationController.status == + AnimationStatus.completed || + chartState._animationController.duration == null + : true); chartState._chartWidgets.add(chartState._renderDataLabel); } } @@ -904,13 +907,12 @@ class _FunnelPlotArea extends StatelessWidget { chartState._tooltipBehaviorRenderer._tooltipTemplate = _TooltipTemplate( show: false, clipRect: chartState._chartContainerRect, - duration: chart.tooltipBehavior.duration); - // chartState._tooltipBehaviorRenderer._sfChart = chart; - // chart.tooltipBehavior._chartState = chartState; + tooltipBehavior: chart.tooltipBehavior, + duration: chart.tooltipBehavior.duration, + chartState: chartState); chartState._chartWidgets .add(chartState._tooltipBehaviorRenderer._tooltipTemplate); } else { - // chartState._tooltipBehaviorRenderer._sfChart = chart; chartState._tooltipBehaviorRenderer._chartTooltip = _ChartTooltipRenderer(chartState: chartState); chartState._chartWidgets @@ -930,7 +932,7 @@ class _FunnelPlotArea extends StatelessWidget { } } if (index != null) { - pointTapArgs = PointTapArgs(0, index, seriesRenderer._dataPoints); + pointTapArgs = PointTapArgs(0, index, seriesRenderer._dataPoints, index); chart.onPointTapped(pointTapArgs); } } @@ -946,14 +948,6 @@ class _FunnelPlotArea extends StatelessWidget { num pointIndex; final FunnelSeriesRenderer seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; - if (chart.onPointTapped != null && seriesRenderer != null) { - _calculatePointSeriesIndex( - chart, seriesRenderer, chartState._tapPosition); - } - if (chart.onDataLabelTapped != null && seriesRenderer != null) { - _triggerFunnelDataLabelEvent( - chart, seriesRenderer, chartState, chartState._tapPosition); - } ChartTouchInteractionArgs touchArgs; for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { if (seriesRenderer._renderPoints[j].isVisible) { @@ -1019,6 +1013,7 @@ class _FunnelPlotArea extends StatelessWidget { chartState._chartSeries ._seriesPointSelection(pointIndex, ActivationMode.doubleTap); if (chart.tooltipBehavior.enable && + chartState._animateCompleted && chart.tooltipBehavior.activationMode == ActivationMode.doubleTap) { if (chart.tooltipBehavior.builder != null) { _showFunnelTooltipTemplate(); @@ -1051,6 +1046,7 @@ class _FunnelPlotArea extends StatelessWidget { } } if (chart.tooltipBehavior.enable && + chartState._animateCompleted && chart.tooltipBehavior.activationMode == ActivationMode.longPress) { if (chart.tooltipBehavior.builder != null) { _showFunnelTooltipTemplate(); @@ -1068,6 +1064,14 @@ class _FunnelPlotArea extends StatelessWidget { chartState._tooltipBehaviorRenderer._isHovering = false; chartState._tapPosition = renderBox.globalToLocal(event.position); ChartTouchInteractionArgs touchArgs; + if (chart.onPointTapped != null && seriesRenderer != null) { + _calculatePointSeriesIndex( + chart, seriesRenderer, chartState._tapPosition); + } + if (chart.onDataLabelTapped != null && seriesRenderer != null) { + _triggerFunnelDataLabelEvent( + chart, seriesRenderer, chartState, chartState._tapPosition); + } if (chartState._tapPosition != null) { if (chartState._currentActive != null && chartState._currentActive.series != null && @@ -1082,6 +1086,7 @@ class _FunnelPlotArea extends StatelessWidget { chartState._currentActive.pointIndex, ActivationMode.singleTap); } if (chart.tooltipBehavior.enable && + chartState._animateCompleted && chart.tooltipBehavior.activationMode == ActivationMode.singleTap && chartState._currentActive != null && chartState._currentActive.series != null) { @@ -1137,6 +1142,7 @@ class _FunnelPlotArea extends StatelessWidget { } if (chartState._tapPosition != null) { if (chart.tooltipBehavior.enable && + chartState._animateCompleted && chartState._currentActive != null && chartState._currentActive.series != null) { chartState._tooltipBehaviorRenderer._isHovering = true; diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/series_base.dart index 70a0dc6ee..9ba6bed1e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/series_base.dart @@ -384,8 +384,20 @@ class _FunnelSeries { final FunnelSeriesRenderer seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[0]; final List selectionData = _chartState._selectionData; + int currentSelectedIndex; if (seriesRenderer._isSelectionEnable && mode == chart.selectionGesture) { if (selectionData.isNotEmpty) { + if (!chart.enableMultiSelection && + _chartState._selectionData.isNotEmpty && + _chartState._selectionData.length > 1) { + if (_chartState._selectionData.contains(pointIndex)) { + currentSelectedIndex = pointIndex; + } + _chartState._selectionData.clear(); + if (currentSelectedIndex != null) { + _chartState._selectionData.add(pointIndex); + } + } for (int i = 0; i < selectionData.length; i++) { final int selectionIndex = selectionData[i]; if (!chart.enableMultiSelection) { @@ -425,10 +437,9 @@ class _FunnelSeries { if (selectionData.isNotEmpty) { for (int i = 0; i < selectionData.length; i++) { final int selectionIndex = selectionData[i]; - if (chart.onSelectionChanged != null && - selectionIndex == currentPointIndex) { + if (chart.onSelectionChanged != null) { chart.onSelectionChanged(_getSelectionEventArgs( - seriesRenderer, seriesIndex, currentPointIndex)); + seriesRenderer, seriesIndex, selectionIndex)); } if (currentPointIndex == selectionIndex) { pointStyle = _StyleOptions( @@ -466,7 +477,8 @@ class _FunnelSeries { SelectionArgs _getSelectionEventArgs( dynamic seriesRenderer, num seriesIndex, num pointIndex) { final FunnelSeries series = seriesRenderer._series; - if (series != null) { + final SfFunnelChart chart = seriesRenderer._chartState._chart; + if (series != null && pointIndex < chart.series.dataSource.length) { final dynamic selectionBehavior = seriesRenderer._selectionBehavior; _selectionArgs = SelectionArgs(seriesRenderer, seriesIndex, pointIndex, pointIndex); diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/data_label_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/data_label_renderer.dart index a3fcfe0b0..a0472cc5c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/data_label_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/data_label_renderer.dart @@ -138,16 +138,19 @@ void _renderFunnelDataLabel( seriesRenderer._series.dataLabelSettings.color; if (chart.onDataLabelRender != null && !seriesRenderer._renderPoints[pointIndex].labelRenderEvent) { - seriesRenderer._renderPoints[pointIndex].labelRenderEvent = true; - dataLabelArgs = DataLabelRenderArgs( - seriesRenderer, seriesRenderer._renderPoints, pointIndex); + dataLabelArgs = DataLabelRenderArgs(seriesRenderer, + seriesRenderer._renderPoints, pointIndex, pointIndex); dataLabelArgs.text = label; dataLabelArgs.textStyle = dataLabelStyle; dataLabelArgs.color = dataLabelSettingsRenderer._color; chart.onDataLabelRender(dataLabelArgs); - label = dataLabelArgs.text; + label = point.text = dataLabelArgs.text; dataLabelStyle = dataLabelArgs.textStyle; + pointIndex = dataLabelArgs.pointIndex; dataLabelSettingsRenderer._color = dataLabelArgs.color; + if (animation.status == AnimationStatus.completed) { + seriesRenderer._dataPoints[pointIndex].labelRenderEvent = true; + } } dataLabelStyle = chart.onDataLabelRender == null ? _getDataLabelTextStyle( @@ -462,14 +465,18 @@ void _triggerFunnelDataLabelEvent( FunnelSeriesRenderer seriesRenderer, SfFunnelChartState chartState, Offset position) { + final int seriesIndex = 0; for (int index = 0; index < seriesRenderer._renderPoints.length; index++) { final PointInfo point = seriesRenderer._renderPoints[index]; + final DataLabelSettings dataLabel = + seriesRenderer._series.dataLabelSettings; final Offset labelLocation = point.symbolLocation; - if (chart.onDataLabelTapped != null && + 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); + point, position, seriesIndex); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_base.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_base.dart index 229005fc3..e4ca52943 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_base.dart @@ -416,6 +416,7 @@ class SfPyramidChartState extends State _legendWidgetContext; // To measure legend size and position List<_ChartTemplateInfo> _templates; // Chart Template info List _chartWidgets; + //ignore: unused_field PyramidSeriesRenderer _seriesRenderer; /// Holds the information of chart theme arguments @@ -475,7 +476,6 @@ class SfPyramidChartState extends State //Update and maintain the series state, when we update the series in the series collection // _createAndUpdateSeriesRenderer(oldWidget); _initialRender = !widget.series.explode; - super.didUpdateWidget(oldWidget); _isLegendToggled = false; _widgetNeedUpdate = true; @@ -491,7 +491,7 @@ class SfPyramidChartState extends State : _deviceOrientation; _deviceOrientation = MediaQuery.of(context).orientation; return RepaintBoundary( - child: Container( + child: _ChartContainer( child: GestureDetector( child: Container( decoration: BoxDecoration( @@ -596,7 +596,7 @@ class SfPyramidChartState extends State _legendToggleStates = <_LegendRenderContext>[]; _legendToggleTemplateStates = <_MeasureWidgetContext>[]; _explodedPoints = []; - _animateCompleted = true; + _animateCompleted = false; _isLegendToggled = false; _widgetNeedUpdate = false; _legendWidgetContext = <_MeasureWidgetContext>[]; @@ -611,7 +611,10 @@ class SfPyramidChartState extends State // In this method, create and update the series renderer for each series // void _createAndUpdateSeriesRenderer([SfPyramidChart oldWidget]) { if (widget.series != null) { - final PyramidSeriesRenderer oldSeriesRenderer = _seriesRenderer; + final PyramidSeriesRenderer oldSeriesRenderer = + oldWidget != null && oldWidget.series != null + ? _chartSeries.visibleSeriesRenderers[0] + : null; dynamic series; series = widget.series; @@ -840,8 +843,7 @@ class _PyramidPlotArea extends StatelessWidget { if (series.animationDuration > 0 && !chartState._didSizeChange && (chartState._deviceOrientation == chartState._oldDeviceOrientation) && - (chartState._initialRender || - chartState._widgetNeedUpdate || + ((!chartState._widgetNeedUpdate && chartState._initialRender) || chartState._isLegendToggled)) { chartState._animationController.duration = Duration(milliseconds: series.animationDuration.toInt()); @@ -887,12 +889,11 @@ class _PyramidPlotArea extends StatelessWidget { .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); chartState._renderDataLabel = _PyramidDataLabelRenderer( chartState: chartState, - show: series.animationDuration > 0 && - (chartState._deviceOrientation == - chartState._oldDeviceOrientation) && - !chartState._didSizeChange - ? false - : chartState._animateCompleted); + show: !chartState._widgetNeedUpdate + ? chartState._animationController.status == + AnimationStatus.completed || + chartState._animationController.duration == null + : true); chartState._chartWidgets.add(chartState._renderDataLabel); } } @@ -904,13 +905,13 @@ class _PyramidPlotArea extends StatelessWidget { chartState._tooltipBehaviorRenderer; tooltip._chartState = chartState; if (tooltip.enable) { - // tooltipBehaviorRenderer._sfChart = chart; - // tooltipBehaviorRenderer._chartState = chartState; if (tooltip.builder != null) { tooltipBehaviorRenderer._tooltipTemplate = _TooltipTemplate( show: false, clipRect: chartState._chartContainerRect, - duration: tooltip.duration); + tooltipBehavior: chart.tooltipBehavior, + duration: tooltip.duration, + chartState: chartState); chartState._chartWidgets.add(tooltipBehaviorRenderer._tooltipTemplate); } else { tooltipBehaviorRenderer._chartTooltip = @@ -932,7 +933,7 @@ class _PyramidPlotArea extends StatelessWidget { } } if (index != null) { - pointTapArgs = PointTapArgs(0, index, seriesRenderer._dataPoints); + pointTapArgs = PointTapArgs(0, index, seriesRenderer._dataPoints, index); chart.onPointTapped(pointTapArgs); } } @@ -950,14 +951,6 @@ class _PyramidPlotArea extends StatelessWidget { chartState._chartSeries.visibleSeriesRenderers; final PyramidSeriesRenderer seriesRenderer = visibleSeriesRenderers[seriesIndex]; - if (chart.onPointTapped != null && seriesRenderer != null) { - _calculatePointSeriesIndex( - chart, seriesRenderer, chartState._tapPosition); - } - if (chart.onDataLabelTapped != null && seriesRenderer != null) { - _triggerPyramidDataLabelEvent( - chart, seriesRenderer, chartState, chartState._tapPosition); - } ChartTouchInteractionArgs touchArgs; for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { if (seriesRenderer._renderPoints[j].isVisible) { @@ -1022,6 +1015,7 @@ class _PyramidPlotArea extends StatelessWidget { chartState._chartSeries ._seriesPointSelection(pointIndex, ActivationMode.doubleTap); if (chart.tooltipBehavior.enable && + chartState._animateCompleted && chart.tooltipBehavior.activationMode == ActivationMode.doubleTap) { if (chart.tooltipBehavior.builder != null) { _showPyramidTooltipTemplate(); @@ -1055,6 +1049,7 @@ class _PyramidPlotArea extends StatelessWidget { } } if (chart.tooltipBehavior.enable && + chartState._animateCompleted && chart.tooltipBehavior.activationMode == ActivationMode.longPress) { if (chart.tooltipBehavior.builder != null) { _showPyramidTooltipTemplate(); @@ -1073,6 +1068,14 @@ class _PyramidPlotArea extends StatelessWidget { final _ChartInteraction currentActive = chartState._currentActive; chartState._tapPosition = renderBox.globalToLocal(event.position); ChartTouchInteractionArgs touchArgs; + if (chart.onPointTapped != null && seriesRenderer != null) { + _calculatePointSeriesIndex( + chart, seriesRenderer, chartState._tapPosition); + } + if (chart.onDataLabelTapped != null && seriesRenderer != null) { + _triggerPyramidDataLabelEvent( + chart, seriesRenderer, chartState, chartState._tapPosition); + } if (chartState._tapPosition != null && chartState._currentActive != null) { if (currentActive.series != null && currentActive.series.explodeGesture == ActivationMode.singleTap) { @@ -1086,6 +1089,7 @@ class _PyramidPlotArea extends StatelessWidget { } if (chart.tooltipBehavior.enable && + chartState._animateCompleted && chart.tooltipBehavior.activationMode == ActivationMode.singleTap && currentActive.series != null) { if (chart.tooltipBehavior.builder != null) { @@ -1145,7 +1149,7 @@ class _PyramidPlotArea extends StatelessWidget { chartState._currentActive != null && chartState._currentActive.series != null) { tooltipBehaviorRenderer._isHovering = true; - if (tooltip.builder != null) { + if (tooltip.builder != null && chartState._animateCompleted) { _showPyramidTooltipTemplate(); } else { final Offset position = renderBox.globalToLocal(event.position); diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/series_base.dart index 6f0eb3d66..effee4fd3 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/series_base.dart @@ -377,8 +377,20 @@ class _PyramidSeries { _chartState._chartSeries.visibleSeriesRenderers[0]; // final PyramidSeries series = seriesRenderer._series; final SfPyramidChartState chartState = _chartState; + int currentSelectedIndex; if (seriesRenderer._isSelectionEnable && mode == chart.selectionGesture) { if (chartState._selectionData.isNotEmpty) { + if (!chart.enableMultiSelection && + _chartState._selectionData.isNotEmpty && + _chartState._selectionData.length > 1) { + if (_chartState._selectionData.contains(pointIndex)) { + currentSelectedIndex = pointIndex; + } + _chartState._selectionData.clear(); + if (currentSelectedIndex != null) { + _chartState._selectionData.add(pointIndex); + } + } for (int i = 0; i < chartState._selectionData.length; i++) { final int selectionIndex = chartState._selectionData[i]; if (!chart.enableMultiSelection) { @@ -416,10 +428,9 @@ class _PyramidSeries { if (_chartState._selectionData.isNotEmpty) { for (int i = 0; i < _chartState._selectionData.length; i++) { final int selectionIndex = _chartState._selectionData[i]; - if (chart.onSelectionChanged != null && - selectionIndex == currentPointIndex) { + if (chart.onSelectionChanged != null) { chart.onSelectionChanged(_getSelectionEventArgs( - seriesRenderer, seriesIndex, currentPointIndex)); + seriesRenderer, seriesIndex, selectionIndex)); } if (currentPointIndex == selectionIndex) { pointStyle = _StyleOptions( @@ -456,7 +467,8 @@ class _PyramidSeries { /// To perform selection event and return selectionArgs SelectionArgs _getSelectionEventArgs( dynamic seriesRenderer, num seriesIndex, num pointIndex) { - if (seriesRenderer != null) { + final SfPyramidChart chart = seriesRenderer._chartState._chart; + if (seriesRenderer != null && pointIndex < chart.series.dataSource.length) { final dynamic selectionBehavior = seriesRenderer._selectionBehavior; _selectionArgs = SelectionArgs(seriesRenderer, seriesIndex, pointIndex, pointIndex); diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/data_label_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/data_label_renderer.dart index 0dc0911fb..e7b90db60 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/data_label_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/data_label_renderer.dart @@ -136,16 +136,19 @@ void _renderPyramidDataLabel( seriesRenderer._series.dataLabelSettings.color; if (chart.onDataLabelRender != null && !seriesRenderer._renderPoints[pointIndex].labelRenderEvent) { - seriesRenderer._renderPoints[pointIndex].labelRenderEvent = true; - dataLabelArgs = DataLabelRenderArgs( - seriesRenderer, seriesRenderer._renderPoints, pointIndex); + dataLabelArgs = DataLabelRenderArgs(seriesRenderer, + seriesRenderer._renderPoints, pointIndex, pointIndex); dataLabelArgs.text = label; dataLabelArgs.textStyle = dataLabelStyle; dataLabelArgs.color = dataLabelSettingsRenderer._color; chart.onDataLabelRender(dataLabelArgs); - label = dataLabelArgs.text; + label = point.text = dataLabelArgs.text; dataLabelStyle = dataLabelArgs.textStyle; + pointIndex = dataLabelArgs.pointIndex; dataLabelSettingsRenderer._color = dataLabelArgs.color; + if (animation.status == AnimationStatus.completed) { + seriesRenderer._dataPoints[pointIndex].labelRenderEvent = true; + } } dataLabelStyle = chart.onDataLabelRender == null ? _getDataLabelTextStyle( @@ -448,16 +451,20 @@ void _triggerPyramidDataLabelEvent( PyramidSeriesRenderer seriesRenderer, SfPyramidChartState chartState, Offset position) { + final int seriesIndex = 0; for (int pointIndex = 0; pointIndex < seriesRenderer._renderPoints.length; pointIndex++) { + final DataLabelSettings dataLabel = + seriesRenderer._series.dataLabelSettings; final PointInfo point = seriesRenderer._renderPoints[pointIndex]; final Offset labelLocation = point.symbolLocation; - if (chart.onDataLabelTapped != null && + 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); + pointIndex, point, position, seriesIndex); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/marker.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/marker.dart new file mode 100644 index 000000000..899270bb3 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/marker.dart @@ -0,0 +1,64 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'utils/enum.dart'; + +/// Represents the marker settings of spark chart +class SparkChartMarker { + /// Creates the marker settings for spark chart + SparkChartMarker( + {this.displayMode = SparkChartMarkerDisplayMode.none, + this.borderColor, + this.borderWidth = 2, + this.color, + this.size = 5, + this.shape = SparkChartMarkerShape.circle}); + + /// Toggles the visibility of the marker. + final SparkChartMarkerDisplayMode displayMode; + + /// Represents the border color of the marker. + final Color borderColor; + + /// Represents the border width of the marker + final double borderWidth; + + /// Represents the color of the marker + final Color color; + + /// Represents the size of the marker + final double size; + + /// Represents the shape of the marker + final SparkChartMarkerShape shape; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is SparkChartMarker && + other.displayMode == displayMode && + other.shape == shape && + other.color == color && + other.size == size && + other.borderColor == borderColor && + other.borderWidth == borderWidth; + } + + @override + int get hashCode { + final List values = [ + displayMode, + shape, + color, + size, + borderColor, + borderWidth, + ]; + return hashList(values); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/plot_band.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/plot_band.dart new file mode 100644 index 000000000..31dc268a4 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/plot_band.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +/// Represents the plot band settings for spark chart +class SparkChartPlotBand { + /// Creates the plot band for spark chart + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// plotBand: SparkChartPlotBand(start: 15, end: 25), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + SparkChartPlotBand( + {this.color = const Color.fromRGBO(191, 212, 252, 0.5), + this.start, + this.end, + this.borderColor, + this.borderWidth = 0}); + + /// Defines the plotband color + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// plotBand: SparkChartPlotBand(start: 15, end: 25, color: Colors.red), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color color; + + /// Defines the start value of plot band + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// plotBand: SparkChartPlotBand(start: 15, end: 25), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double start; + + /// Defines the end value of plot band + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// plotBand: SparkChartPlotBand(start: 15, end: 25), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double end; + + /// Defines the border color of plot band + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// plotBand: SparkChartPlotBand(start: 15, end: 25, borderColor: Colors.black, + /// borderWidth: 2), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color borderColor; + + /// Defines the border width of plot band + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// plotBand: SparkChartPlotBand(start: 15, end: 25, borderColor: Colors.black, + /// borderWidth: 2), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double borderWidth; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is SparkChartPlotBand && + other.start == start && + other.end == end && + other.color == color && + other.borderColor == borderColor && + other.borderWidth == borderWidth; + } + + @override + int get hashCode { + final List values = [ + start, + end, + color, + borderColor, + borderWidth, + ]; + return hashList(values); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/renderer_base.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/renderer_base.dart new file mode 100644 index 000000000..7879585b6 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/renderer_base.dart @@ -0,0 +1,712 @@ +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show DateFormat; +import '../plot_band.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Represents the render object for spark chart +abstract class SfSparkChartRenderObjectWidget extends LeafRenderObjectWidget { + /// Creates the render object for spark chart + 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; + + /// Field in the data source, which is considered as x-value. + final SparkChartIndexedValueMapper xValueMapper; + + /// Field in the data source, which is considered as y-value. + final SparkChartIndexedValueMapper yValueMapper; + + /// Specifies the data source count + final int dataCount; + + /// Specifies whether to inverse the spark chart + final bool isInversed; + + /// Specifies the horizontal axis line position. + final double axisCrossesAt; + + /// Specifies the horizontal axis line width + final double axisLineWidth; + + /// Specifies the axis line color + final Color axisLineColor; + + /// Specifies the axis line dash array + final List axisLineDashArray; + + /// Specifies the high point color + final Color highPointColor; + + /// Specifies the low point color + final Color lowPointColor; + + /// Specifies the negative point color + final Color negativePointColor; + + /// Specifies the first point color + final Color firstPointColor; + + /// Specifies the last point color + final Color lastPointColor; + + /// Specifies the spark chart color + final Color color; + + /// Specifies the spark chart plot band + final SparkChartPlotBand plotBand; + + /// Specifies the spark chart data details + final SparkChartDataDetails sparkChartDataDetails; + + /// Specfies the theme of the spark chart + final ThemeData themeData; + + /// Specifies the series screen coordinate points + final List coordinatePoints; + + /// Specifies the series data points + final List dataPoints; +} + +/// Represents the RenderSparkChart class +abstract class RenderSparkChart extends RenderBox { + /// Creates the render object widget + RenderSparkChart( + {Widget child, + List data, + int dataCount, + SparkChartIndexedValueMapper xValueMapper, + SparkChartIndexedValueMapper yValueMapper, + bool isInversed, + double axisCrossesAt, + double axisLineWidth, + Color axisLineColor, + List axisLineDashArray, + Color color, + Color firstPointColor, + Color lastPointColor, + Color highPointColor, + Color lowPointColor, + Color negativePointColor, + SparkChartPlotBand plotBand, + SparkChartDataDetails sparkChartDataDetails, + ThemeData themeData, + List coordinatePoints, + List dataPoints}) + : _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) { + inverseDataPoints(); + } + } + + /// Defines the data source + List _data; + + /// Returns the data source value + List get data => _data; + + /// Set the data source value + set data(List value) { + if (_data != value) { + _data = value; + _refreshSparkChart(); + markNeedsPaint(); + } + } + + /// Defines the data count + int _dataCount; + + /// Returns the data count value + int get dataCount => _dataCount; + + /// Set the data source value + set dataCount(int value) { + if (_dataCount != value) { + _dataCount = value; + _refreshSparkChart(); + markNeedsPaint(); + } + } + + /// Defines the x-value in the data source. + SparkChartIndexedValueMapper _xValueMapper; + + /// Returns the xValueMapper value + SparkChartIndexedValueMapper get xValueMapper => _xValueMapper; + + /// Set the xValue Mapper value + set xValueMapper(SparkChartIndexedValueMapper value) { + if (_xValueMapper != value) { + _xValueMapper = value; + _refreshSparkChart(); + markNeedsPaint(); + } + } + + /// Defines the y-value in the data source. + SparkChartIndexedValueMapper _yValueMapper; + + /// Returns the yValueMapper value + SparkChartIndexedValueMapper get yValueMapper => _yValueMapper; + + /// Set the yValue Mapper value + set yValueMapper(SparkChartIndexedValueMapper value) { + if (_yValueMapper != value) { + _yValueMapper = value; + _refreshSparkChart(); + markNeedsPaint(); + } + } + + /// Defines whether to inverse the spark chart + bool _isInversed; + + /// Returns the isInversed + bool get isInversed => _isInversed; + + /// Set the isInversed + set isInversed(bool value) { + if (_isInversed != value) { + _isInversed = value; + inverseDataPoints(); + calculateRenderingPoints(); + markNeedsPaint(); + } + } + + /// Defines the horizontal axis line position. + double _axisCrossesAt; + + /// Returns the axisCrossesAt value + double get axisCrossesAt => _axisCrossesAt; + + /// Set the axisCrossesAt value + set axisCrossesAt(double value) { + if (_axisCrossesAt != value) { + _axisCrossesAt = value; + axisHeight = getAxisHeight(); + markNeedsPaint(); + } + } + + /// Defines the axis line width + double _axisLineWidth; + + /// Returns the axis line width value + double get axisLineWidth => _axisLineWidth; + + /// Set the axis line width value + set axisLineWidth(double value) { + if (_axisLineWidth != value) { + _axisLineWidth = value; + markNeedsPaint(); + } + } + + /// Defines the axis line color + Color _axisLineColor; + + /// Returns the axis line color value + Color get axisLineColor => _axisLineColor; + + /// Set the axis line color value + set axisLineColor(Color value) { + if (_axisLineColor != value) { + _axisLineColor = value; + markNeedsPaint(); + } + } + + /// Defines the spark chart theme + ThemeData _themeData; + + /// Returns the spark chart theme + ThemeData get themeData => _themeData; + + /// Sets the spark chart theme + set themeData(ThemeData value) { + if (_themeData != value) { + _themeData = value; + markNeedsPaint(); + } + } + + /// Defines the series coordinate points + List _coordinatePoints; + + /// Returns the series coordinate points + List get coordinatePoints => _coordinatePoints; + + /// Sets the series coordinate points + set coordinatePoints(List value) { + if (_coordinatePoints != value) { + _coordinatePoints = value; + } + } + + /// Defines the series data points + List _dataPoints; + + /// Returns the series data points + List get dataPoints => _dataPoints; + + /// Sets the series coordinate points + set dataPoints(List value) { + if (_dataPoints != value) { + _dataPoints = value; + } + } + + /// Defines the axis line dash array + List _axisLineDashArray; + + /// Returns the axis line dash array value + List get axisLineDashArray => _axisLineDashArray; + + /// Set the axis line dash array value + set axisLineDashArray(List value) { + if (_axisLineDashArray != value) { + _axisLineDashArray = value; + markNeedsPaint(); + } + } + + /// Defines the first point color + Color _firstPointColor; + + /// Returns the first point color value + Color get firstPointColor => _firstPointColor; + + /// Set the first point color value + set firstPointColor(Color value) { + if (_firstPointColor != value) { + _firstPointColor = value; + markNeedsPaint(); + } + } + + /// Defines the last point color + Color _lastPointColor; + + /// Returns the last point color vlue + Color get lastPointColor => _lastPointColor; + + /// Set the last point color value + set lastPointColor(Color value) { + if (_lastPointColor != value) { + _lastPointColor = value; + markNeedsPaint(); + } + } + + /// Defines the high point color + Color _highPointColor; + + /// Returns the high point color value + Color get highPointColor => _highPointColor; + + /// Set the high point color value + set highPointColor(Color value) { + if (_highPointColor != value) { + _highPointColor = value; + markNeedsPaint(); + } + } + + /// Defines the low point color + Color _lowPointColor; + + /// Returns the low point color value + Color get lowPointColor => _lowPointColor; + + /// Set the low point color value + set lowPointColor(Color value) { + if (_lowPointColor != value) { + _lowPointColor = value; + markNeedsPaint(); + } + } + + /// Defines the negative point color + Color _negativePointColor; + + /// Returns the negative point color value + Color get negativePointColor => _negativePointColor; + + /// Set the negative point color value + set negativePointColor(Color value) { + if (_negativePointColor != value) { + _negativePointColor = value; + markNeedsPaint(); + } + } + + /// Defines the spark chart series color + Color _color; + + /// Returns the spark chart color + Color get color => _color; + + /// Set the spark chart color value + set color(Color value) { + if (_color != value) { + _color = value; + markNeedsPaint(); + } + } + + /// Defines the spark chart plot band + SparkChartPlotBand _plotBand; + + /// Returns the spark chart plot band + SparkChartPlotBand get plotBand => _plotBand; + + /// Sets the spark chart plot band + set plotBand(SparkChartPlotBand value) { + if (_plotBand != value) { + _plotBand = value; + calculatePlotBandPosition(); + markNeedsPaint(); + } + } + + /// Defines the spark chart data details + SparkChartDataDetails _sparkChartDataDetails; + + /// Returns the spark chart data details + SparkChartDataDetails get sparkChartDataDetails => _sparkChartDataDetails; + + /// Sets the spark chart data details + set sparkChartDataDetails(SparkChartDataDetails value) { + if (_sparkChartDataDetails != value) { + _sparkChartDataDetails = value; + } + } + + /// Defines the plot band start height + double plotBandStartHeight; + + /// Defines the plot band end height + double plotBandEndHeight; + + /// Specifies the minimum X value + double minX; + + /// Specifies the maximum X value + double maxX; + + /// Specifies the minimum Y value + double minY; + + /// Specifies the maximum X value + double maxY; + + /// Defines the Y difference + double diffY; + + /// Defines the X difference + double diffX; + + /// Specifies the axis height + double axisHeight; + + /// Specifies the area size + Size areaSize; + + /// specifies the data label values + List dataLabels; + + /// specifies the data label values + List reversedDataLabels; + + /// Method to find the minX, maxX, minY, maxY + void _calculateMinimumMaximumXY(SparkChartPoint currentPoint) { + minX ??= currentPoint.x.toDouble(); + maxX ??= currentPoint.x.toDouble(); + minX = math.min(minX, currentPoint.x.toDouble()); + maxX = math.max(maxX, currentPoint.x.toDouble()); + minY ??= currentPoint.y.toDouble(); + maxY ??= currentPoint.y.toDouble(); + minY = math.min(minY, currentPoint.y.toDouble()); + maxY = math.max(maxY, currentPoint.y.toDouble()); + } + + /// Method to process the data source + void processDataSource() { + if (dataPoints.isNotEmpty) { + dataPoints.clear(); + } + + dataLabels = []; + reversedDataLabels = []; + minX = maxX = minY = maxY = null; + SparkChartPoint currentPoint; + String labelY; + if (data != null && data.isNotEmpty && data is List) { + for (int i = 0; i < data.length; i++) { + if (data[i] != null) { + currentPoint = SparkChartPoint(x: i, y: data[i]); + labelY = _getDataLabel(data[i]); + currentPoint.labelY = labelY; + _calculateMinimumMaximumXY(currentPoint); + dataPoints.add(currentPoint); + dataLabels.add(_getDataLabel(data[i])); + } + } + } else { + dynamic xValue; + dynamic yValue; + String labelX; + dynamic actualX; + if (xValueMapper != null && + yValueMapper != null && + dataCount != null && + dataCount > 0) { + for (int i = 0; i < dataCount; i++) { + xValue = xValueMapper(i); + actualX = xValue; + if (xValue is String) { + labelX = xValue.toString(); + xValue = i.toDouble(); + } else if (xValue is DateTime) { + xValue = xValue.millisecondsSinceEpoch; + labelX = DateFormat.yMd() + .format(DateTime.fromMillisecondsSinceEpoch(xValue)); + } else if (xValue is num) { + labelX = _getDataLabel(xValue); + } + + yValue = yValueMapper(i); + labelY = _getDataLabel(yValue); + if (xValue != null && yValue != null) { + currentPoint = SparkChartPoint(x: xValue, y: yValue); + currentPoint.actualX = actualX; + currentPoint.labelX = labelX; + currentPoint.labelY = labelY; + _calculateMinimumMaximumXY(currentPoint); + dataPoints.add(currentPoint); + dataLabels.add(_getDataLabel(currentPoint.y)); + } + } + } + } + } + + /// Returns the data label + String _getDataLabel(num value) { + 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) { + value = value.round(); + } + } + dataLabel = value.toString(); + return dataLabel; + } + + /// 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) + ? axisHeight + : areaSize.height; + if (value >= minY && value <= maxY) { + axisLineHeight = areaSize.height - + (areaSize.height * ((value - minY) / diffY)).roundToDouble(); + } + return axisLineHeight; + } + + /// Inverse the data Points + void inverseDataPoints() { + final List temp = dataPoints.reversed.toList(); + reversedDataLabels = List.from(dataLabels.reversed); + dataLabels.clear(); + dataLabels.addAll(reversedDataLabels); + dataPoints.clear(); + dataPoints.addAll(temp); + final double tempX = minX; + minX = maxX; + maxX = tempX; + } + + /// 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); + } + } + + /// Method to calculate the plot band position + void calculatePlotBandPosition() { + final double height = areaSize.height; + final double start = plotBand == null + ? 0 + : (plotBand.start ?? minY) < minY + ? minY + : (plotBand.start ?? minY); + final double end = plotBand == null + ? 0 + : (plotBand.end ?? maxY) > maxY + ? maxY + : (plotBand.end ?? maxY); + plotBandStartHeight = (height - ((height / diffY) * (start - minY))); + plotBandEndHeight = (height - ((height / diffY) * (end - minY))); + } + + /// Method to render axis line + void renderAxisline(Canvas canvas, Offset offset) { + if (axisLineWidth > 0 && axisHeight != null) { + 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; + if (axisLineDashArray != null && axisLineDashArray.isNotEmpty) { + drawDashedPath(canvas, paint, point1, point2, axisLineDashArray); + } else { + canvas.drawLine(point1, point2, paint); + } + } + } + + /// Method to render plot band + void renderPlotBand(Canvas canvas, Offset offset) { + 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); + } + } else { + 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); + canvas.drawLine(point1, point2, paint); + } + } + + /// Method to refresh the spark chart + void _refreshSparkChart() { + processDataSource(); + if (isInversed) { + inverseDataPoints(); + } + calculateRenderingPoints(); + if (plotBand != null) { + calculatePlotBandPosition(); + } + } + + @override + void performLayout() { + size = Size(constraints.maxWidth, constraints.maxHeight); + areaSize = size; + calculateRenderingPoints(); + if (plotBand != null) { + calculatePlotBandPosition(); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + renderAxisline(context.canvas, offset); + + 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 new file mode 100644 index 000000000..d54f62603 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_area_renderer.dart @@ -0,0 +1,458 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter/rendering.dart'; +import '../marker.dart'; +import '../plot_band.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'renderer_base.dart'; + +/// Represents the render object for spark chart +class SfSparkAreaChartRenderObjectWidget + extends SfSparkChartRenderObjectWidget { + /// Creates the render object for spark chart + SfSparkAreaChartRenderObjectWidget({ + Key key, + double borderWidth, + Color borderColor, + List data, + int dataCount, + SparkChartIndexedValueMapper xValueMapper, + SparkChartIndexedValueMapper yValueMapper, + bool isInversed, + double axisCrossesAt, + Color axisLineColor, + double axisLineWidth, + List axisLineDashArray, + Color firstPointColor, + Color lowPointColor, + Color highPointColor, + Color lastPointColor, + Color negativePointColor, + Color color, + SparkChartPlotBand plotBand, + SparkChartMarker marker, + SparkChartLabelDisplayMode labelDisplayMode, + TextStyle labelStyle, + ThemeData themeData, + SparkChartDataDetails sparkChartDataDetails, + List coordinatePoints, + List dataPoints, + }) : borderWidth = borderWidth, + borderColor = borderColor, + marker = marker, + labelDisplayMode = labelDisplayMode, + labelStyle = labelStyle, + 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); + + /// Specifies the area chart border width + final double borderWidth; + + /// Specifies the area chart border color + final Color borderColor; + + /// Specifies the area chart marker + final SparkChartMarker marker; + + /// Specifies the spark chart data label + final SparkChartLabelDisplayMode labelDisplayMode; + + /// Specifies the spark chart data label style + final TextStyle labelStyle; + + @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); + } + + @override + void updateRenderObject( + BuildContext context, _RenderSparkAreaChart renderObject) { + renderObject + ..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 + ..themeData = themeData + ..dataPoints = dataPoints + ..coordinatePoints = coordinatePoints; + } +} + +/// 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, + ThemeData 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; + + /// Returns the border width value + double get borderWidth => _borderWidth; + + /// Set the border width value + set borderWidth(double value) { + if (_borderWidth != value) { + _borderWidth = value; + markNeedsPaint(); + } + } + + /// Defines the dash array. + Color _borderColor; + + /// Returns the dash arry value + Color get borderColor => _borderColor; + + /// Set the line width value + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsPaint(); + } + } + + /// Defines the marker for spark chart + SparkChartMarker _marker; + + /// Gets the marker for spark chart + SparkChartMarker get marker => _marker; + + /// Sets the marker for spark chart + set marker(SparkChartMarker value) { + if (_marker != value) { + _marker = value; + markNeedsPaint(); + } + } + + /// Defines the spark chart data label mode + SparkChartLabelDisplayMode _labelDisplayMode; + + /// Returns the spark chart data label mode + SparkChartLabelDisplayMode get labelDisplayMode => _labelDisplayMode; + + /// Sets the spark chart data label mode + set labelDisplayMode(SparkChartLabelDisplayMode value) { + if (_labelDisplayMode != value) { + _labelDisplayMode = value; + markNeedsPaint(); + } + } + + /// Defines the spark chart data label text style + TextStyle _labelStyle; + + /// Returns the spark chart data label text style + TextStyle get labelStyle => _labelStyle; + + /// Sets the spark chart data label mode + set labelStyle(TextStyle value) { + if (_labelStyle != value) { + _labelStyle = value; + markNeedsPaint(); + } + } + + /// Specifies the low point in series + num _lowPoint; + + /// Specifies the high point in series + num _highPoint; + + @override + void processDataSource() { + super.processDataSource(); + if (dataPoints != null && dataPoints.isNotEmpty) { + final List temp = List.from(dataPoints); + final List tempDataLabels = List.from(dataLabels); + dataLabels.clear(); + dataPoints.clear(); + 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); + point2.labelX = temp[temp.length - 1].labelX; + point2.labelY = temp[temp.length - 1].labelY; + dataPoints.add(point2); + dataLabels.add('0'); + dataLabels.addAll(tempDataLabels); + dataLabels.add('0'); + } + } + + /// Render area series + void _renderAreaSeries(Canvas canvas, Offset offset) { + final Paint paint = Paint() + ..color = color + ..style = PaintingStyle.fill; + final Path path = Path(); + Size size; + _highPoint = coordinatePoints[0].dy; + _lowPoint = coordinatePoints[0].dy; + + for (int i = 0; i < coordinatePoints.length; i++) { + if (_highPoint < coordinatePoints[i].dy) { + _highPoint = coordinatePoints[i].dy; + } + + if (_lowPoint > coordinatePoints[i].dy) { + _lowPoint = coordinatePoints[i].dy; + } + + if (i == 0) { + path.moveTo(offset.dx + coordinatePoints[i].dx, + offset.dy + coordinatePoints[i].dy); + } + + if (i < coordinatePoints.length - 1) { + path.lineTo(offset.dx + coordinatePoints[i + 1].dx, + offset.dy + coordinatePoints[i + 1].dy); + } + + if (i >= 1 && + i <= coordinatePoints.length - 2 && + labelDisplayMode != SparkChartLabelDisplayMode.none && + labelStyle != null) { + size = getTextSize(dataLabels[i], labelStyle); + dataPoints[i].dataLabelOffset = Offset( + offset.dx + coordinatePoints[i].dx, + 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); + } + + if (dataPoints[i].dataLabelOffset.dx >= offset.dx + areaSize.width) { + dataPoints[i].dataLabelOffset = Offset( + ((offset.dx + areaSize.width) - size.width), + dataPoints[i].dataLabelOffset.dy); + } + + if (dataPoints[i].dataLabelOffset.dy <= offset.dy) { + dataPoints[i].dataLabelOffset = Offset( + dataPoints[i].dataLabelOffset.dx, + (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)); + } + } + } + + canvas.drawPath(path, paint); + if (borderColor != null && + borderColor != Colors.transparent && + borderWidth != null && + borderWidth > 0) { + _renderAreaSeriesBorder(canvas, offset); + } + } + + /// 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 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); + } + + if (i < coordinatePoints.length - 2) { + strokePath.lineTo(offset.dx + coordinatePoints[i + 1].dx, + offset.dy + coordinatePoints[i + 1].dy); + } + } + + canvas.drawPath(strokePath, strokePaint); + } + + @override + void paint(PaintingContext context, Offset offset) { + super.paint(context, offset); + if (coordinatePoints != null && + coordinatePoints.isNotEmpty && + dataPoints != null && + dataPoints.isNotEmpty) { + _renderAreaSeries(context.canvas, offset); + if (marker != null && + marker.displayMode != SparkChartMarkerDisplayMode.none) { + renderMarker( + context.canvas, + offset, + marker, + coordinatePoints, + dataPoints, + color, + 'Area', + _highPoint, + _lowPoint, + themeData, + lowPointColor, + highPointColor, + negativePointColor, + firstPointColor, + lastPointColor); + } + if (labelDisplayMode != SparkChartLabelDisplayMode.none) { + renderDataLabel( + 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 new file mode 100644 index 000000000..314b7fd56 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_bar_renderer.dart @@ -0,0 +1,461 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter/rendering.dart'; +import '../plot_band.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'renderer_base.dart'; + +/// Represents the render object for spark chart +class SfSparkBarChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { + /// Creates the render object for spark chart + SfSparkBarChartRenderObjectWidget( + {Key key, + double borderWidth, + Color borderColor, + List data, + int dataCount, + SparkChartIndexedValueMapper xValueMapper, + SparkChartIndexedValueMapper yValueMapper, + bool isInversed, + double axisCrossesAt, + Color axisLineColor, + double axisLineWidth, + List axisLineDashArray, + Color firstPointColor, + Color lowPointColor, + Color highPointColor, + Color lastPointColor, + Color negativePointColor, + Color color, + SparkChartPlotBand plotBand, + SparkChartLabelDisplayMode labelDisplayMode, + TextStyle labelStyle, + ThemeData themeData, + SparkChartDataDetails sparkChartDataDetails, + List coordinatePoints, + List dataPoints}) + : borderWidth = borderWidth, + borderColor = borderColor, + labelDisplayMode = labelDisplayMode, + labelStyle = labelStyle, + 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; + + /// Specifies the bar chart border color + final Color borderColor; + + /// Specifies the spark chart data label + final SparkChartLabelDisplayMode labelDisplayMode; + + /// Specifies the spark chart data label style + final TextStyle labelStyle; + + @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); + } + + @override + void updateRenderObject( + BuildContext context, _RenderSparkBarChart renderObject) { + renderObject + ..isInversed = isInversed + ..axisCrossesAt = axisCrossesAt + ..axisLineColor = axisLineColor + ..axisLineWidth = axisLineWidth + ..axisLineDashArray = axisLineDashArray + ..dataCount = dataCount + ..data = data + ..xValueMapper = xValueMapper + ..yValueMapper = yValueMapper + ..firstPointColor = firstPointColor + ..lastPointColor = lastPointColor + ..highPointColor = highPointColor + ..lowPointColor = lowPointColor + ..negativePointColor = negativePointColor + ..color = color + ..plotBand = plotBand + ..borderColor = borderColor + ..borderWidth = borderWidth + ..labelDisplayMode = labelDisplayMode + ..labelStyle = labelStyle + ..themeData = themeData + ..coordinatePoints = coordinatePoints + ..dataPoints = dataPoints; + } +} + +/// 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, + ThemeData 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; + + /// Returns the border width value + double get borderWidth => _borderWidth; + + /// Set the border width value + set borderWidth(double value) { + if (_borderWidth != value) { + _borderWidth = value; + markNeedsPaint(); + } + } + + /// Defines the dash array. + Color _borderColor; + + /// Returns the dash arry value + Color get borderColor => _borderColor; + + /// Set the line width value + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsPaint(); + } + } + + /// Defines the spark chart data label mode + SparkChartLabelDisplayMode _labelDisplayMode; + + /// Returns the spark chart data label mode + SparkChartLabelDisplayMode get labelDisplayMode => _labelDisplayMode; + + /// Sets the spark chart data label mode + set labelDisplayMode(SparkChartLabelDisplayMode value) { + if (_labelDisplayMode != value) { + _labelDisplayMode = value; + markNeedsPaint(); + } + } + + /// Defines the spark chart data label text style + TextStyle _labelStyle; + + /// Returns the spark chart data label text style + TextStyle get labelStyle => _labelStyle; + + /// Sets the spark chart data label mode + set labelStyle(TextStyle value) { + if (_labelStyle != value) { + _labelStyle = value; + markNeedsPaint(); + } + } + + /// Defines the horizontal axis line position. + double _axisCrossesAt; + + /// Returns the axisCrossesAt value + @override + double get axisCrossesAt => _axisCrossesAt; + + /// Set the axisCrossesAt value + @override + set axisCrossesAt(double value) { + if (_axisCrossesAt != value) { + _axisCrossesAt = value; + calculateRenderingPoints(); + markNeedsPaint(); + } + } + + /// Specifies the win loss segments + List _segments; + + /// Specifies the low point in series + num _lowPoint; + + /// Specifies the high point in series + num _highPoint; + + @override + void calculateRenderingPoints() { + diffX = maxX - minX; + diffY = maxY - minY; + diffX = diffX == 0 ? 1 : diffX; + diffY = diffY == 0 ? 1 : diffY; + + _segments = []; + final double xInterval = + dataPoints[1].x.toDouble() - dataPoints[0].x.toDouble(); + final double columnSpace = 0.5; // Default space for column and winloss + final double space = columnSpace * 2; + final axisBaseValue = minY < 0 ? minY : 0; + double visibleXPoint; + Rect rect; + double top; + double x, y, y2; + double columnHeight; + double currentColumnHeight; + double columnWidth = areaSize.width / (((maxX - minX) / xInterval) + 1); + columnWidth -= space; + diffY = maxY - axisBaseValue; + axisHeight = getAxisHeight(); + if (coordinatePoints.isNotEmpty) { + coordinatePoints.clear(); + } + + for (int i = 0; i < dataPoints.length; i++) { + x = dataPoints[i].x.toDouble(); + y = dataPoints[i].y.toDouble(); + visibleXPoint = + (((x - minX) / xInterval) * (columnWidth + space)) + (space / 2); + columnHeight = (areaSize.height / diffY) * (y - axisBaseValue); + currentColumnHeight = (y == axisBaseValue && y > axisCrossesAt) + ? ((dataPoints.length != 1 && diffY != 1) + ? (areaSize.height / diffY) * axisBaseValue + : (columnHeight.toInt() | 1)) + : (y == maxY && + y < axisCrossesAt && + dataPoints.length != 1 && + diffY != 1) + ? (areaSize.height / diffY) * maxY + : columnHeight; + y2 = (areaSize.height - currentColumnHeight).abs(); + top = (y2 > axisHeight) ? axisHeight : y2; + rect = Rect.fromLTRB(visibleXPoint, top, visibleXPoint + columnWidth, + top + (y2 - axisHeight).abs()); + _segments.add(rect); + final double yPoint = y >= axisCrossesAt ? rect.top : rect.bottom; + coordinatePoints.add(Offset(visibleXPoint + columnWidth / 2, yPoint)); + } + } + + /// Method to calculate axis height + @override + double getAxisHeight() { + final double value = axisCrossesAt; + final double minimumColumnValue = minY < 0 ? minY : 0; + double axisLineHeight = + areaSize.height - ((areaSize.height / diffY) * (-minY)); + axisLineHeight = (minY < 0 && maxY <= 0) + ? 0 + : (minY < 0 && maxY > 0) + ? axisHeight + : areaSize.height; + if (value >= minimumColumnValue && value <= maxY) { + axisLineHeight = areaSize.height - + (areaSize.height * ((value - minimumColumnValue) / diffY)) + .roundToDouble(); + } + return axisLineHeight; + } + + /// Method to calculate the plot band position + @override + void calculatePlotBandPosition() { + final double height = areaSize.height; + final double start = + (plotBand.start ?? minY) < minY ? minY : (plotBand.start ?? minY); + final double end = + (plotBand.end ?? maxY) > maxY ? maxY : (plotBand.end ?? maxY); + final double baseValue = minY < 0 ? minY : 0; + plotBandStartHeight = (height - ((height / diffY) * (start - baseValue))); + plotBandEndHeight = (height - ((height / diffY) * (end - baseValue))); + } + + /// Method to render bar series + void _renderBarSeries(Canvas canvas, Offset offset) { + Color currentColor; + Paint paint; + final Paint strokePaint = Paint() + ..color = borderColor ?? Colors.transparent + ..strokeWidth = borderWidth ?? 0 + ..style = PaintingStyle.stroke; + + Size size; + double yPosition; + final bool canDrawBorder = borderColor != null && + borderColor != Colors.transparent && + borderWidth != null && + borderWidth > 0; + Rect rect; + _highPoint = coordinatePoints[0].dy; + _lowPoint = coordinatePoints[0].dy; + for (int i = 0; i < _segments.length; i++) { + if (_highPoint < coordinatePoints[i].dy) { + _highPoint = coordinatePoints[i].dy; + } + + if (_lowPoint > coordinatePoints[i].dy) { + _lowPoint = coordinatePoints[i].dy; + } + rect = Rect.fromLTRB( + _segments[i].left + offset.dx, + _segments[i].top + offset.dy, + _segments[i].right + offset.dx, + _segments[i].bottom + offset.dy); + if (dataPoints[i].y == maxY && highPointColor != null) { + currentColor = highPointColor; + } else if (dataPoints[i].y == minY && lowPointColor != null) { + currentColor = lowPointColor; + } else if (i == 0 && firstPointColor != null) { + currentColor = firstPointColor; + } else if (i == _segments.length - 1 && lastPointColor != null) { + currentColor = lastPointColor; + } else if (dataPoints[i].y < axisCrossesAt && + negativePointColor != null) { + currentColor = negativePointColor; + } else { + currentColor = color; + } + dataPoints[i].color = currentColor; + paint = Paint()..color = currentColor; + canvas.drawRect(rect, paint); + if (canDrawBorder) { + canvas.drawRect(rect, strokePaint); + } + + if (labelDisplayMode != SparkChartLabelDisplayMode.none && + labelStyle != null) { + size = getTextSize(dataLabels[i], labelStyle); + yPosition = (dataPoints[i].y > 0 + ? ((_segments[i].topCenter.dy + offset.dy) - size.height) + : ((_segments[i].bottomCenter.dy + offset.dy))); + dataPoints[i].dataLabelOffset = Offset( + (offset.dx + _segments[i].topCenter.dx) - size.width / 2, + yPosition); + + if (dataPoints[i].dataLabelOffset.dy <= offset.dy) { + dataPoints[i].dataLabelOffset = Offset( + dataPoints[i].dataLabelOffset.dx, (offset.dy + size.height)); + } + if (dataPoints[i].dataLabelOffset.dy >= offset.dy + areaSize.height) { + dataPoints[i].dataLabelOffset = Offset( + dataPoints[i].dataLabelOffset.dx, + (offset.dy + areaSize.height) - size.height); + } + } + } + } + + @override + void paint(PaintingContext context, Offset offset) { + super.paint(context, offset); + if (coordinatePoints != null && + coordinatePoints.isNotEmpty && + dataPoints != null && + dataPoints.isNotEmpty) { + _renderBarSeries(context.canvas, offset); + if (labelDisplayMode != SparkChartLabelDisplayMode.none) { + renderDataLabel( + 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 new file mode 100644 index 000000000..0e19760dd --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_line_renderer.dart @@ -0,0 +1,447 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter/rendering.dart'; +import '../marker.dart'; +import '../plot_band.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'renderer_base.dart'; + +/// Represents the render object for spark chart +class SfSparkLineChartRenderObjectWidget + extends SfSparkChartRenderObjectWidget { + /// Creates the render object for spark chart + SfSparkLineChartRenderObjectWidget( + {Key key, + double width, + List dashArray, + List data, + int dataCount, + SparkChartIndexedValueMapper xValueMapper, + SparkChartIndexedValueMapper yValueMapper, + bool isInversed, + double axisCrossesAt, + Color axisLineColor, + double axisLineWidth, + List axisLineDashArray, + Color firstPointColor, + Color lowPointColor, + Color highPointColor, + Color lastPointColor, + Color negativePointColor, + Color color, + SparkChartPlotBand plotBand, + SparkChartMarker marker, + SparkChartLabelDisplayMode labelDisplayMode, + TextStyle labelStyle, + ThemeData themeData, + SparkChartDataDetails sparkChartDataDetails, + List coordinatePoints, + List dataPoints}) + : width = width, + dashArray = dashArray, + marker = marker, + labelDisplayMode = labelDisplayMode, + labelStyle = labelStyle, + super( + key: key, + 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; + + /// Specifies the dash array + final List dashArray; + + /// Specifies the area chart marker + final SparkChartMarker marker; + + /// Specifies the spark chart data label + final SparkChartLabelDisplayMode labelDisplayMode; + + /// Specifies the spark chart data label style + final TextStyle labelStyle; + + @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); + } + + @override + void updateRenderObject( + BuildContext context, _RenderSparkLineChart renderObject) { + renderObject + ..isInversed = isInversed + ..axisCrossesAt = axisCrossesAt + ..axisLineColor = axisLineColor + ..axisLineWidth = axisLineWidth + ..axisLineDashArray = axisLineDashArray + ..dataCount = dataCount + ..data = data + ..xValueMapper = xValueMapper + ..yValueMapper = yValueMapper + ..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 + ..coordinatePoints = coordinatePoints + ..dataPoints = dataPoints; + } +} + +/// 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, + ThemeData 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; + + /// Returns the line width value + double get width => _width; + + /// Set the line width value + set width(double value) { + if (_width != value) { + _width = value; + markNeedsPaint(); + } + } + + /// Defines the dash array. + List _dashArray; + + /// Returns the dash arry value + List get dashArray => _dashArray; + + /// Set the line width value + set dashArray(List value) { + if (_dashArray != value) { + _dashArray = value; + markNeedsPaint(); + } + } + + /// Defines the marker for spark chart + SparkChartMarker _marker; + + /// Gets the marker for spark chart + SparkChartMarker get marker => _marker; + + /// Sets the marker for spark chart + set marker(SparkChartMarker value) { + if (_marker != value) { + _marker = value; + markNeedsPaint(); + } + } + + /// Defines the spark chart data label mode + SparkChartLabelDisplayMode _labelDisplayMode; + + /// Returns the spark chart data label mode + SparkChartLabelDisplayMode get labelDisplayMode => _labelDisplayMode; + + /// Sets the spark chart data label mode + set labelDisplayMode(SparkChartLabelDisplayMode value) { + if (_labelDisplayMode != value) { + _labelDisplayMode = value; + markNeedsPaint(); + } + } + + /// Defines the spark chart data label text style + TextStyle _labelStyle; + + /// Returns the spark chart data label text style + TextStyle get labelStyle => _labelStyle; + + /// Sets the spark chart data label mode + set labelStyle(TextStyle value) { + if (_labelStyle != value) { + _labelStyle = value; + markNeedsPaint(); + } + } + + /// Specifies the low point in series + num _lowPoint; + + /// Specifies the high point in series + num _highPoint; + + /// 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; + + Size size; + double yPosition; + _highPoint = coordinatePoints[0].dy; + _lowPoint = coordinatePoints[0].dy; + if (dashArray != null && dashArray.isNotEmpty) { + Offset point1, point2; + for (int i = 0; i < coordinatePoints.length; i++) { + if (_highPoint < coordinatePoints[i].dy) { + _highPoint = coordinatePoints[i].dy; + } + + if (_lowPoint > coordinatePoints[i].dy) { + _lowPoint = coordinatePoints[i].dy; + } + + if (i < coordinatePoints.length - 1) { + point1 = Offset(offset.dx + coordinatePoints[i].dx, + offset.dy + coordinatePoints[i].dy); + point2 = Offset(offset.dx + coordinatePoints[i + 1].dx, + offset.dy + coordinatePoints[i + 1].dy); + 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 + ? (coordinatePoints[i].dy - size.height) + : (coordinatePoints[i].dy)); + dataPoints[i].dataLabelOffset = Offset( + offset.dx + coordinatePoints[i].dx, offset.dy + yPosition); + + _positionDataLabels(dataPoints[i], size, offset); + } + } + } else { + final Path path = Path(); + for (int i = 0; i < coordinatePoints.length; i++) { + if (_highPoint < coordinatePoints[i].dy) { + _highPoint = coordinatePoints[i].dy; + } + + if (_lowPoint > coordinatePoints[i].dy) { + _lowPoint = coordinatePoints[i].dy; + } + + if (i == 0) { + path.moveTo(offset.dx + coordinatePoints[i].dx, + offset.dy + coordinatePoints[i].dy); + } + + if (i < coordinatePoints.length - 1) { + path.lineTo(offset.dx + coordinatePoints[i + 1].dx, + offset.dy + coordinatePoints[i + 1].dy); + } + if (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 + ? (coordinatePoints[i].dy - size.height) + : (coordinatePoints[i].dy)); + dataPoints[i].dataLabelOffset = Offset( + offset.dx + coordinatePoints[i].dx, offset.dy + yPosition); + + _positionDataLabels(dataPoints[i], size, offset); + } + } + + canvas.drawPath(path, paint); + } + } + } + + void _positionDataLabels( + SparkChartPoint dataPoint, Size size, Offset offset) { + if (dataPoint.dataLabelOffset.dx <= offset.dx) { + 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); + } + + 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))); + } + + 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)); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + super.paint(context, offset); + if (coordinatePoints != null && + coordinatePoints.isNotEmpty && + dataPoints != null && + dataPoints.isNotEmpty) { + _renderLineSeries(context.canvas, offset); + + if (marker != null && + marker.displayMode != SparkChartMarkerDisplayMode.none) { + renderMarker( + context.canvas, + offset, + marker, + coordinatePoints, + dataPoints, + color, + 'Line', + _highPoint, + _lowPoint, + themeData, + lowPointColor, + highPointColor, + negativePointColor, + firstPointColor, + lastPointColor); + } + if (labelDisplayMode != SparkChartLabelDisplayMode.none) { + renderDataLabel( + 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 new file mode 100644 index 000000000..f95046034 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_win_loss_renderer.dart @@ -0,0 +1,327 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter/rendering.dart'; +import '../plot_band.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'renderer_base.dart'; + +/// Represents the render object for spark chart +class SfSparkWinLossChartRenderObjectWidget + extends SfSparkChartRenderObjectWidget { + /// Creates the render object for spark chart + SfSparkWinLossChartRenderObjectWidget( + {Key key, + List data, + int dataCount, + SparkChartIndexedValueMapper xValueMapper, + SparkChartIndexedValueMapper yValueMapper, + Color color, + SparkChartPlotBand plotBand, + double borderWidth, + Color borderColor, + Color tiePointColor, + bool isInversed, + double axisCrossesAt, + Color axisLineColor, + double axisLineWidth, + List axisLineDashArray, + Color firstPointColor, + Color lowPointColor, + Color highPointColor, + Color lastPointColor, + Color negativePointColor, + SparkChartDataDetails sparkChartDataDetails, + ThemeData themeData, + List coordinatePoints, + List dataPoints}) + : borderWidth = borderWidth, + borderColor = borderColor, + tiePointColor = tiePointColor, + 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; + + /// Specifies the area chart border color + final Color borderColor; + + /// Specifies the tie point color + final Color tiePointColor; + + @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); + } + + @override + void updateRenderObject( + BuildContext context, _RenderSparkWinLossChart renderObject) { + renderObject + ..isInversed = isInversed + ..axisCrossesAt = axisCrossesAt + ..axisLineColor = axisLineColor + ..axisLineWidth = axisLineWidth + ..axisLineDashArray = axisLineDashArray + ..dataCount = dataCount + ..data = data + ..xValueMapper = xValueMapper + ..yValueMapper = yValueMapper + ..firstPointColor = firstPointColor + ..lastPointColor = lastPointColor + ..highPointColor = highPointColor + ..lowPointColor = lowPointColor + ..negativePointColor = negativePointColor + ..color = color + ..plotBand = plotBand + ..tiePointColor = tiePointColor + ..borderColor = borderColor + ..borderWidth = borderWidth + ..themeData = themeData + ..coordinatePoints = coordinatePoints + ..dataPoints = dataPoints; + } +} + +/// 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, + ThemeData 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; + + /// Returns the border width value + double get borderWidth => _borderWidth; + + /// Set the border width value + set borderWidth(double value) { + if (_borderWidth != value) { + _borderWidth = value; + markNeedsPaint(); + } + } + + /// Defines the border color. + Color _borderColor; + + /// Returns the border color + Color get borderColor => _borderColor; + + /// Set the border color value + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsPaint(); + } + } + + /// Defines the tie point color. + Color _tiePointColor; + + /// Returns the tie point color + Color get tiePointColor => _tiePointColor; + + /// Set the tie point color + set tiePointColor(Color value) { + if (_tiePointColor != value) { + _tiePointColor = value; + markNeedsPaint(); + } + } + + /// Specifies the win loss segments + List _segments; + + @override + void calculateRenderingPoints() { + diffX = maxX - minX; + diffY = maxY - minY; + diffX = diffX == 0 ? 1 : diffX; + diffY = diffY == 0 ? 1 : diffY; + + _segments = []; + final double xInterval = + dataPoints[1].x.toDouble() - dataPoints[0].x.toDouble(); + final double columnSpace = 0.5; // Default space for column and winloss + final double space = columnSpace * 2; + final double winLossFactor = 0.5; + final double heightFactor = 40; + double visibleXPoint; + double rectHeight; + double bottom; + Rect rect; + double x, y, y2; + double columnWidth = areaSize.width / (((maxX - minX) / xInterval) + 1); + columnWidth -= space; + axisHeight = getAxisHeight(); + if (coordinatePoints.isNotEmpty) { + coordinatePoints.clear(); + } + + for (int i = 0; i < dataPoints.length; i++) { + x = dataPoints[i].x.toDouble(); + y = dataPoints[i].y.toDouble(); + + visibleXPoint = + (((x - minX) / xInterval) * (columnWidth + space)) + (space / 2); + y2 = (y > axisCrossesAt) + ? (areaSize.height / 2) + : (y < axisCrossesAt) + ? areaSize.height * winLossFactor + : ((areaSize.height * winLossFactor) - + (areaSize.height / heightFactor)); + rectHeight = + (y != axisCrossesAt) ? (areaSize.height / 2) : areaSize.height / 20; + bottom = y > axisCrossesAt ? rectHeight - y2 : rectHeight + y2; + rect = + Rect.fromLTRB(visibleXPoint, y2, visibleXPoint + columnWidth, bottom); + _segments.add(rect); + coordinatePoints.add(Offset(visibleXPoint + columnWidth / 2, y2)); + } + } + + /// 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 paint = Paint()..color = color; + final Paint strokePaint = Paint() + ..color = borderColor ?? Colors.transparent + ..strokeWidth = borderWidth ?? 0 + ..style = PaintingStyle.stroke; + + final bool canDrawBorder = borderColor != null && + borderColor != Colors.transparent && + borderWidth != null && + borderWidth > 0; + Rect rect; + + for (int i = 0; i < dataPoints.length; i++) { + rect = Rect.fromLTRB( + _segments[i].left + offset.dx, + _segments[i].top + offset.dy, + _segments[i].right + offset.dx, + _segments[i].bottom + offset.dy); + if (dataPoints[i].y < axisCrossesAt) { + canvas.drawRect(rect, negativePointPaint); + } else if (dataPoints[i].y == axisCrossesAt) { + canvas.drawRect(rect, tiePointPaint); + } else { + canvas.drawRect(rect, paint); + } + + if (canDrawBorder) { + canvas.drawRect(rect, strokePaint); + } + } + } + + @override + void paint(PaintingContext context, Offset offset) { + if (coordinatePoints != null && + coordinatePoints.isNotEmpty && + dataPoints != null && + dataPoints.isNotEmpty) { + if (plotBand != null) { + renderPlotBand(context.canvas, offset); + } + + _renderWinLossSeries(context.canvas, offset); + } + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_area_base.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_area_base.dart new file mode 100644 index 000000000..0e6d96bb8 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_area_base.dart @@ -0,0 +1,513 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../marker.dart'; +import '../plot_band.dart'; +import '../renderers/spark_area_renderer.dart'; +import '../trackball/spark_chart_trackball.dart'; +import '../trackball/trackball_renderer.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Represents the spark area chart widget +class SfSparkAreaChart extends StatefulWidget { + /// Creates the spark area chart + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + 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); + + /// Create the spark area chart with custom data source + /// + /// ```dart + /// class SalesData { + /// SalesData(this.month, this.sales); + /// final String month; + /// final double sales; + /// } + /// + /// List data; + + /// @override + /// void initState() { + /// super.initState(); + /// data = [ + /// SalesData('Jan', 200), + /// SalesData('Feb', 215), + /// SalesData('Mar', 380), + /// SalesData('Apr', 240), + /// SalesData('May', 280), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart.custom( + /// dataCount: 5, + /// xValueMapper: (int index) => data[index].month, + /// yValueMapper: (int index) => data[index].sales + /// )), + /// ); + /// } + /// ``` + + 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); + + /// Specifies whether to inverse the spark area chart rendering. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// isInversed: true, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final bool isInversed; + + /// Specifies the axis line's position. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// axisCrossesAt: 14, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double axisCrossesAt; + + /// Specifies the width of the axis line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// axisLineWidth: 4, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double axisLineWidth; + + /// Specified the color of the axis line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// axisLineColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color axisLineColor; + + /// Specifies the dash array value of the axis line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// axisLineDashArray: [2,2], + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final List axisLineDashArray; + + /// Specifies the highest data point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// highPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color highPointColor; + + /// Specifies the lowest data point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// lowPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color lowPointColor; + + /// Specifies the negative point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// negativePointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color negativePointColor; + + /// Specifies the first point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// firstPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color firstPointColor; + + /// Specifies the last point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// lastPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color lastPointColor; + + /// Specifies the color of the spark area chart. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// color: Colors.blue, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color color; + + /// Represents the plot band settings for spark area chart. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// plotBand: SparkChartPlotBand(start: 15, end: 25), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final SparkChartPlotBand plotBand; + + /// Specifies the Border width of the series. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// boderWidth: 2, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double borderWidth; + + /// Specifies the Border color of the series. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// boderWidth: 2, + /// borderColor: Colors.black, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color borderColor; + + /// Represents the marker settings of spark chart. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// boderWidth: 2, + /// marker: SparkChartMarker(borderWidth: 3, size: 20, + /// shape: MarkerShape.circle, displayMode: MarkerDisplayMode.all), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + + final SparkChartMarker marker; + + /// Specifies the spark area data label + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// boderWidth: 2, + /// labelDisplayMode: SparkChartLabelDisplayode.high, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final SparkChartLabelDisplayMode labelDisplayMode; + + /// Specifies the spark area data label + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// boderWidth: 2, + /// labelStyle: TextStyle(fontStyle: FontStyle.italic), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final TextStyle labelStyle; + + /// Represents the track ball options of spark area chart. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:(borderWidth: 2, + /// borderColor: Colors.black, SparkChartActivationMode: ActivationMode.doubleTap), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final SparkChartTrackball trackball; + + /// Specifies the spark chart data details + final SparkChartDataDetails _sparkChartDataDetails; + + @override + State createState() { + return _SfSparkAreaChartState(); + } +} + +/// Represents the state class for spark area widget +class _SfSparkAreaChartState extends State { + /// specifies the theme of the chart + ThemeData _themeData; + + /// Specifies the series screen coordinate points + List _coordinatePoints; + + /// Specifies the series data points + List _dataPoints; + + @override + void initState() { + _coordinatePoints = []; + _dataPoints = []; + super.initState(); + } + + @override + void didUpdateWidget(SfSparkAreaChart oldWidget) { + super.didUpdateWidget(oldWidget); + } + + @override + void didChangeDependencies() { + _themeData = Theme.of(context); + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext 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())); + } else { + return RepaintBoundary(child: _getSparkAreaChart()); + } + } + + /// 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: _themeData, + sparkChartDataDetails: widget._sparkChartDataDetails, + dataPoints: _dataPoints, + coordinatePoints: _coordinatePoints), + SparkChartTrackballRenderer( + trackball: widget.trackball, + coordinatePoints: _coordinatePoints, + dataPoints: _dataPoints, + 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 new file mode 100644 index 000000000..fb7642169 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_bar_base.dart @@ -0,0 +1,476 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../plot_band.dart'; +import '../renderers/spark_bar_renderer.dart'; +import '../trackball/spark_chart_trackball.dart'; +import '../trackball/trackball_renderer.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Represents the spark bar chart widget +class SfSparkBarChart extends StatefulWidget { + /// Creates the spark bar chart + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + 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); + + /// Create the spark bar chart with custom data source + /// + /// ```dart + /// class SalesData { + /// SalesData(this.month, this.sales); + /// final String month; + /// final double sales; + /// } + /// + /// List data; + + /// @override + /// void initState() { + /// super.initState(); + /// data = [ + /// SalesData('Jan', 200), + /// SalesData('Feb', 215), + /// SalesData('Mar', 380), + /// SalesData('Apr', 240), + /// SalesData('May', 280), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart.custom( + /// dataCount: 5, + /// xValueMapper: (int index) => data[index].month, + /// yValueMapper: (int index) => data[index].sales + /// )), + /// ); + /// } + /// ``` + 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); + + /// Specifies whether to inverse the spark bar chart rendering. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// isInversed: true, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final bool isInversed; + + /// Specifies the axis line's position. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// axisCrossesAt: 14, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double axisCrossesAt; + + /// Specifies the width of the axis line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// axisLineWidth: 4, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double axisLineWidth; + + /// Specified the color of the axis line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// axisLineColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color axisLineColor; + + /// Specifies the dash array value of the axis line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// axisLineDashArray: [2,2], + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final List axisLineDashArray; + + /// Specifies the highest data point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// highPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color highPointColor; + + /// Specifies the lowest data point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// lowPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color lowPointColor; + + /// Specifies the negative point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// negativePointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color negativePointColor; + + /// Specifies the first point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// firstPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color firstPointColor; + + /// Specifies the last point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// lastPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color lastPointColor; + + /// Specifies the color of the spark bar chart. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// color: Colors.blue, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color color; + + /// Represents the plot band settings for spark bar chart. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// plotBand: SparkChartPlotBand(start: 15, end: 25), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final SparkChartPlotBand plotBand; + + /// Specifies the Border width of the series. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// borderColor: Colors.black, borderWidth: 3, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double borderWidth; + + /// Specifies the Border color of the series. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// borderColor: Colors.black, borderWidth: 3, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color borderColor; + + /// Specifies the spark area data label + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// boderWidth: 2, + /// labelDisplayMode: SparkChartLabelDisplayode.high, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final SparkChartLabelDisplayMode labelDisplayMode; + + /// Specifies the spark bar data label + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// boderWidth: 2, + /// labelStyle: TextStyle(fontStyle: FontStyle.italic), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final TextStyle labelStyle; + + /// Represents the track ball options of spark bar chart. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkBarChart( + /// trackball:(borderWidth: 2, + /// borderColor: Colors.black, activationMode: SparkChartActivationMode.doubleTap), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final SparkChartTrackball trackball; + + /// Specifies the spark chart data details + final SparkChartDataDetails _sparkChartDataDetails; + @override + State createState() { + return _SfSparkBarChartState(); + } +} + +/// Represents the state class for spark bar widget +class _SfSparkBarChartState extends State { + /// specifies the theme of the chart + ThemeData _themeData; + + /// Specifies the series screen coordinate points + List _coordinatePoints; + + /// Specifies the series data points + List _dataPoints; + + @override + void initState() { + _coordinatePoints = []; + _dataPoints = []; + super.initState(); + } + + @override + void didChangeDependencies() { + _themeData = Theme.of(context); + super.didChangeDependencies(); + } + + @override + void didUpdateWidget(SfSparkBarChart oldWidget) { + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext 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: _themeData, + sparkChartDataDetails: widget._sparkChartDataDetails, + dataPoints: _dataPoints, + coordinatePoints: _coordinatePoints), + SparkChartTrackballRenderer( + trackball: widget.trackball, + coordinatePoints: _coordinatePoints, + dataPoints: _dataPoints, + 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 new file mode 100644 index 000000000..2e059e7fd --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_line_base.dart @@ -0,0 +1,512 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../marker.dart'; +import '../plot_band.dart'; +import '../renderers/spark_line_renderer.dart'; +import '../trackball/spark_chart_trackball.dart'; +import '../trackball/trackball_renderer.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Represents the spark line chart +class SfSparkLineChart extends StatefulWidget { + /// Creates the spark line chart + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + 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); + + /// Create the spark line chart with custom data source + /// + /// ```dart + /// class SalesData { + /// SalesData(this.month, this.sales); + /// final String month; + /// final double sales; + /// } + /// + /// List data; + + /// @override + /// void initState() { + /// super.initState(); + /// data = [ + /// SalesData('Jan', 200), + /// SalesData('Feb', 215), + /// SalesData('Mar', 380), + /// SalesData('Apr', 240), + /// SalesData('May', 280), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart.custom( + /// dataCount: 5, + /// xValueMapper: (int index) => data[index].month, + /// yValueMapper: (int index) => data[index].sales + /// )), + /// ); + /// } + /// ``` + + 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); + + /// Specifies whether to inverse the spark line chart rendering. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// isInversed: true, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final bool isInversed; + + /// Specifies the axis line's position. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// axisCrossesAt: 14, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double axisCrossesAt; + + /// Specifies the width of the axis line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// axisLineWidth: 4, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double axisLineWidth; + + /// Specified the color of the axis line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// axisLineColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color axisLineColor; + + /// Specifies the dash array value of the axis line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// axisLineDashArray: [2,2], + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final List axisLineDashArray; + + /// Specifies the highest data point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// highPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color highPointColor; + + /// Specifies the lowest data point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// lowPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color lowPointColor; + + /// Specifies the negative point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// negativePointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color negativePointColor; + + /// Specifies the first point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// firstPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color firstPointColor; + + /// Specifies the last point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// lastPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color lastPointColor; + + /// Specifies the color of the spark line chart. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// color: Colors.blue, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color color; + + /// Represents the plot band settings for spark line chart. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// plotBand: SparkChartPlotBand(start: 15, end: 25), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final SparkChartPlotBand plotBand; + + /// Specifies the width of the series. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// width: 4, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double width; + + /// Specifies the Dash array value of series. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// dashArray: [2,2], + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final List dashArray; + + /// Represents the track ball options of spark line chart. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkLineChart( + /// trackball:(borderWidth: 2, + /// borderColor: Colors.black, activationMode: SparkChartActivationMode.doubleTap), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final SparkChartTrackball trackball; + + /// Represents the marker settings of spark chart. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// boderWidth: 2, + /// marker: SparkChartMarker(borderWidth: 3, size: 20, + /// shape: MarkerShape.circle, displayMode: MarkerDisplayMode.all), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + + final SparkChartMarker marker; + + /// Specifies the spark area data label + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// boderWidth: 2, + /// labelDisplayMode: SparkChartLabelDisplayode.high, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final SparkChartLabelDisplayMode labelDisplayMode; + + /// Specifies the spark line data label + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// boderWidth: 2, + /// labelStyle: TextStyle(fontStyle: FontStyle.italic), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final TextStyle labelStyle; + + /// Specifies the spark chart data details + final SparkChartDataDetails _sparkChartDataDetails; + + @override + State createState() { + return _SfSparkLineChartState(); + } +} + +/// Represents the state class for spark line chart widget +class _SfSparkLineChartState extends State { + /// specifies the theme of the chart + ThemeData _themeData; + + /// Specifies the series screen coordinate points + List _coordinatePoints; + + /// Specifies the series data points + List _dataPoints; + + @override + void initState() { + _coordinatePoints = []; + _dataPoints = []; + super.initState(); + } + + @override + void didChangeDependencies() { + _themeData = Theme.of(context); + super.didChangeDependencies(); + } + + @override + void didUpdateWidget(SfSparkLineChart oldWidget) { + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext 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: _getSparkLineChart())); + } else { + return RepaintBoundary(child: _getSparkLineChart()); + } + } + + /// Method to return the spark line chart widget + Widget _getSparkLineChart() { + 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: _themeData, + sparkChartDataDetails: widget._sparkChartDataDetails, + dataPoints: _dataPoints, + coordinatePoints: _coordinatePoints), + SparkChartTrackballRenderer( + trackball: widget.trackball, + coordinatePoints: _coordinatePoints, + dataPoints: _dataPoints, + ) + ])); + } +} 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 new file mode 100644 index 000000000..b9a5bcee8 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_win_loss_base.dart @@ -0,0 +1,436 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../plot_band.dart'; +import '../renderers/spark_win_loss_renderer.dart'; +import '../trackball/spark_chart_trackball.dart'; +import '../trackball/trackball_renderer.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Represents the spark win loss chart widget +class SfSparkWinLossChart extends StatefulWidget { + /// Creates the spark win loss chart + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + 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); + + /// Create the spark win loss chart with custom data source + /// + /// ```dart + /// class SalesData { + /// SalesData(this.month, this.sales); + /// final String month; + /// final double sales; + /// } + /// + /// List data; + + /// @override + /// void initState() { + /// super.initState(); + /// data = [ + /// SalesData('Jan', 200), + /// SalesData('Feb', 215), + /// SalesData('Mar', 380), + /// SalesData('Apr', 240), + /// SalesData('May', 280), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart.custom( + /// dataCount: 5, + /// xValueMapper: (int index) => data[index].month, + /// yValueMapper: (int index) => data[index].sales + /// )), + /// ); + /// } + /// ``` + 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); + + /// Specifies whether to inverse the spark chart rendering. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// isInversed: true, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final bool isInversed; + + /// Specifies the axis line's position. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// axisCrossesAt: 14, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double axisCrossesAt; + + /// Specifies the width of the axis line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// axisLineWidth: 4, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double axisLineWidth; + + /// Specified the color of the axis line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// axisLineColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color axisLineColor; + + /// Specifies the dash array value of the axis line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// axisLineDashArray: [2,2], + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final List axisLineDashArray; + + /// Specifies the highest data point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// highPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color highPointColor; + + /// Specifies the lowest data point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// lowPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color lowPointColor; + + /// Specifies the negative point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// negativePointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color negativePointColor; + + /// Specifies the first point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// firstPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color firstPointColor; + + /// Specifies the last point color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// lastPointColor: Colors.red, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color lastPointColor; + + /// Specifies the color of the spark win loss chart. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// color: Colors.blue, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color color; + + /// Represents the plot band settings for spark win loss chart. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// plotBand: SparkChartPlotBand(start: 15, end: 25), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final SparkChartPlotBand plotBand; + + /// Specifies the Border width of the series. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// width: 4, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double borderWidth; + + /// Specifies the Border color of the series. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// dashArray: [2,2], + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color borderColor; + + /// Tie point color of Win loss series. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// dashArray: [2,2], + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color tiePointColor; + + /// Represents the track ball options of spark win loss chart + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkWinLossChart( + /// trackball:(borderWidth: 2, + /// borderColor: Colors.black, activationMode: SparkChartActivationMode.doubleTap), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + final SparkChartTrackball trackball; + + /// Specifies the spark chart data details + final SparkChartDataDetails _sparkChartDataDetails; + + @override + State createState() { + return _SfSparkWinLossChartState(); + } +} + +/// Represents the state class for spark win loss chart widget +class _SfSparkWinLossChartState extends State { + /// Specifies the series screen coordinate points + List _coordinatePoints; + + /// Specifies the series data points + List _dataPoints; + + @override + void initState() { + _coordinatePoints = []; + _dataPoints = []; + super.initState(); + } + + @override + void didUpdateWidget(SfSparkWinLossChart oldWidget) { + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext 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, + sparkChartDataDetails: widget._sparkChartDataDetails, + dataPoints: _dataPoints, + coordinatePoints: _coordinatePoints), + SparkChartTrackballRenderer( + trackball: widget.trackball, + coordinatePoints: _coordinatePoints, + dataPoints: _dataPoints, + ) + ]))); + } +} 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 new file mode 100644 index 000000000..9031306f2 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/spark_chart_trackball.dart @@ -0,0 +1,289 @@ +import 'dart:ui'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../utils/enum.dart'; + +/// Represents the track ball behavior of spark chart widget +class SparkChartTrackball { + /// Creates the track ball behavior of spark chart widget + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:(borderWidth: 2, + /// borderColor: Colors.black, activationMode: SparkChartActivationMode.doubleTap), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + 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))}); + + /// Represents the width of track ball line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:(width: 5, + /// ), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double width; + + /// Represents the color of track ball line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:( + /// color: Colors.black,), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color color; + + /// Represents the dash array for track ball line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:(dashArray: [2,2], + /// ), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final List dashArray; + + /// Represents the activation mode of track ball line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:(SparkChartActivationMode: ActivationMode.doubleTap), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final SparkChartActivationMode activationMode; + + /// Represents the label style of the track ball. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:(labelStyle: TextStyle(fontSize: 15)), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final TextStyle labelStyle; + + /// Represents the background color for track ball tooltip. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:( + /// backgroundColor: Colors.black), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color backgroundColor; + + /// Represents the background color for track ball tooltip. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:(borderWidth: 2, + /// borderColor: Colors.black,), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color borderColor; + + /// Repreents the border width of trackball tooltip. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:(boderWidth: 2, + /// borderColor: Colors.black, activationMode: SparkChartActivationMode.doubleTap), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double borderWidth; + + /// Repreents the border radius of trackball tooltip. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:(BorderRadius.all(Radius.circular(3)), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final BorderRadius borderRadius; + + /// Shows or hides the trackball. + /// + /// By default, the trackball will be hidden on touch. To avoid this, set this property to true. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:(shouldAlwaysShow: true, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final bool shouldAlwaysShow; + + /// The trackball disappears after this time interval. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:(shouldAlwaysShow: true, + /// hideDelay: 200, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final double hideDelay; + + /// Callback for formatting tooltip text. + /// + /// String handleTooltipFormatter(TooltipFormatterDetails details) { + /// return details.y.toStringAsFixed(0) + 'cm'; + /// } + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// trackball:( + /// tooltipFormatter: handleTooltipFormatter, + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final SparkChartTooltipCallback tooltipFormatter; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is SparkChartTrackball && + other.width == width && + other.color == color && + other.activationMode == activationMode && + other.labelStyle == labelStyle && + other.backgroundColor == backgroundColor && + other.borderColor == borderColor && + other.borderWidth == borderWidth && + other.shouldAlwaysShow == shouldAlwaysShow && + other.hideDelay == hideDelay && + other.borderRadius == borderRadius && + other.tooltipFormatter == tooltipFormatter && + listEquals(other.dashArray, dashArray); + } + + @override + int get hashCode { + final List values = [ + width, + color, + activationMode, + labelStyle, + backgroundColor, + borderColor, + borderWidth, + dashArray, + shouldAlwaysShow, + hideDelay, + borderRadius, + tooltipFormatter + ]; + return hashList(values); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/trackball_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/trackball_renderer.dart new file mode 100644 index 000000000..4671d6c09 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/trackball_renderer.dart @@ -0,0 +1,487 @@ +import 'dart:async'; +import 'dart:ui'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../series/spark_area_base.dart'; +import '../series/spark_bar_base.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'spark_chart_trackball.dart'; + +/// Represents the trackball renderer +class SparkChartTrackballRenderer extends StatefulWidget { + /// Creates the trackball renderer + SparkChartTrackballRenderer( + {Key key, + this.trackball, + this.coordinatePoints, + this.dataPoints, + this.sparkChart}) + : super(key: key); + + /// Specifies the spark chart trackball + final SparkChartTrackball trackball; + + /// Specifies the coordinate points + final List coordinatePoints; + + /// Specifies the spark chart data points + final List dataPoints; + + /// Specifie the spark chart widget + final Widget sparkChart; + + @override + State createState() { + return _SparckChartTrackballRendererState(); + } +} + +/// Represents the state class of spark chart trackball renderer +class _SparckChartTrackballRendererState + extends State { + /// Holds the trackball repaint notifier + ValueNotifier _trackballRepaintNotifier; + + /// Specifies whether the track ball is enabled + bool _isTrackballEnabled = false; + + /// Specifies the current touch position + Offset _touchPosition; + + /// Specifies the spark area bounds + Rect _areaBounds; + + /// Specifies the local rect + Rect _localBounds; + + /// Specifies the nearest point index + int _currentIndex; + + /// Specifies the global position + Offset _globalPosition; + + /// Specifies the theme of the chart + ThemeData _themeData; + + /// Specifies the current data point + SparkChartPoint _currentDataPoint; + + /// Specifies the current coordinate point + Offset _currentCoordinatePoint; + + /// Specifies the trackball timer + Timer _timer; + + /// Specifies whether to render the trackball on top + bool _isTop; + + @override + void initState() { + _trackballRepaintNotifier = ValueNotifier(0); + super.initState(); + } + + @override + void didUpdateWidget(SparkChartTrackballRenderer oldWidget) { + super.didUpdateWidget(oldWidget); + } + + @override + void didChangeDependencies() { + _themeData = Theme.of(context); + super.didChangeDependencies(); + } + + @override + void dispose() { + if (_timer != null) { + _timer.cancel(); + } + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MouseRegion( + onHover: (PointerHoverEvent event) => + _enableAndUpdateTrackball(context, event.position), + onExit: (event) => _hide(), + child: Listener( + onPointerUp: (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, + 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) + ? (TapDownDetails details) => + _enableAndUpdateTrackball(context, details.globalPosition) + : null, + onLongPressStart: (widget.trackball != null && widget.trackball.activationMode == SparkChartActivationMode.longPress) + ? (LongPressStartDetails details) => + _enableAndUpdateTrackball(context, details.globalPosition) + : null, + onLongPressMoveUpdate: (widget.trackball != null && widget.trackball.activationMode == SparkChartActivationMode.longPress) ? (LongPressMoveUpdateDetails details) => _updateDragValue(context, details.globalPosition) : null, + onDoubleTapDown: (widget.trackball != null && widget.trackball.activationMode == SparkChartActivationMode.doubleTap) ? (TapDownDetails details) => _enableTrackballBehavior(context, details.globalPosition) : null, + onDoubleTap: (widget.trackball != null && widget.trackball.activationMode == SparkChartActivationMode.doubleTap) ? () => _updateDragValue(context, _globalPosition) : null, + child: _addTrackballPainter()), + )); + } + + /// Method to hide the trackball + void _hide() { + if (widget.trackball != null && !widget.trackball.shouldAlwaysShow) { + if (_timer != null) { + _timer.cancel(); + } + + _trackballRepaintNotifier.value++; + _timer = + Timer(Duration(milliseconds: widget.trackball.hideDelay.toInt()), () { + _trackballRepaintNotifier.value++; + _endTrackballDragging(); + }); + } + } + + /// Method to add the trackball painter + Widget _addTrackballPainter() { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Container( + child: RepaintBoundary( + child: CustomPaint( + painter: TrackballPainter(_trackballRepaintNotifier, + _isTrackballEnabled, widget.trackball, this), + size: Size(constraints.maxWidth, constraints.maxHeight)), + ), + ); + }, + ); + } + + /// Method to enable the trackball behavior + void _enableTrackballBehavior(BuildContext context, Offset globalPosition) { + final RenderBox renderBox = context.findRenderObject(); + 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); + _globalPosition = globalPosition; + _touchPosition = renderBox.globalToLocal(globalPosition); + if (_localBounds.contains(_touchPosition)) { + _isTrackballEnabled = true; + } + } + + /// Method to disable the trackball dragging + void _endTrackballDragging() { + if (_isTrackballEnabled) { + _isTrackballEnabled = false; + _touchPosition = null; + _globalPosition = null; + _currentIndex = null; + _currentDataPoint = null; + _currentCoordinatePoint = null; + _isTop = false; + } + } + + /// Method to update the trackball value + void _updateDragValue(BuildContext context, Offset globalPosition) { + _currentIndex = null; + _isTop = false; + num index; + if (_isTrackballEnabled) { + final RenderBox renderBox = context.findRenderObject(); + _touchPosition = renderBox.globalToLocal(globalPosition); + final double currentXPoint = _touchPosition.dx; + double xPoint; + double temp; + double diff; + for (int i = 0; i < widget.coordinatePoints.length; i++) { + xPoint = widget.coordinatePoints[i].dx; + diff = (currentXPoint - xPoint).abs(); + if (temp == null || temp > diff) { + temp = diff; + index = i; + } + } + + if (index != null) { + if (index == 0 && widget.sparkChart is SfSparkAreaChart) { + index = 1; + } else if (widget.sparkChart is SfSparkBarChart) { + _isTop = true; + } + _currentDataPoint = widget.dataPoints[index]; + _currentCoordinatePoint = widget.coordinatePoints[index]; + _trackballRepaintNotifier.value++; + } + + _currentIndex = index; + } + } + + /// Method to enable and update the track ball value + void _enableAndUpdateTrackball(BuildContext context, Offset globalPosition) { + _enableTrackballBehavior(context, globalPosition); + _updateDragValue(context, globalPosition); + } +} + +/// 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); + + /// Specifies whether to repaint the series + final bool _isRepaint; + + /// Specifies the trackball of spark chart + final SparkChartTrackball _trackball; + + /// Specifies the trackball renderer state + final _SparckChartTrackballRendererState _rendererState; + + @override + void paint(Canvas canvas, Size size) { + final num index = _rendererState._currentIndex; + if (index != null) { + final Offset screenPoint = _rendererState._currentCoordinatePoint; + _drawTrackLine(canvas, _rendererState._areaBounds, screenPoint, size); + _renderTrackballTooltip(canvas, screenPoint, index); + } + } + + /// Method to render the trackball tooltip + void _renderTrackballTooltip(Canvas canvas, Offset screenPoint, num index) { + Offset labelOffset = screenPoint; + final String dataLabel = _getTrackballLabel(); + final TextStyle labelStyle = _getTrackballLabelStyle(); + final Size textSize = getTextSize(dataLabel, labelStyle); + final Rect areaBounds = _rendererState._areaBounds; + BorderRadius borderRadius = _trackball.borderRadius; + double rectWidth = textSize.width; + if (rectWidth < 10) { + rectWidth = 10; + borderRadius = _getBorderRadius(borderRadius, rectWidth / 2); + } + + final double textWidth = textSize.height / 2; + borderRadius = _getBorderRadius(borderRadius, textWidth); + Rect labelRect = Rect.fromLTWH(screenPoint.dx, screenPoint.dy, + textSize.width + 15, textSize.height + 10); + final double tooltipPadding = 5; + final double pointerWidth = 5; + final double pointerLength = 7; + final double totalWidth = areaBounds.right - areaBounds.left; + final double totalHeight = areaBounds.bottom - areaBounds.top; + final bool isTop = _rendererState._isTop; + bool isRight = false; + double xPosition; + double yPosition; + bool isBottom = false; + if (screenPoint != null) { + if (!isTop) { + xPosition = screenPoint.dx + pointerLength + tooltipPadding; + yPosition = screenPoint.dy - labelRect.height / 2; + if ((xPosition + labelRect.width) > totalWidth) { + xPosition = ((xPosition - labelRect.width - (2 * tooltipPadding)) - + (2 * pointerLength)); + isRight = true; + } else if (xPosition >= totalWidth) { + xPosition = totalWidth - (xPosition + labelRect.width); + isRight = true; + } + + if (yPosition <= 0) { + yPosition = 0; + } else if (yPosition + labelRect.height >= totalHeight) { + yPosition = totalHeight - labelRect.height; + } + } else { + final double padding = 2; + xPosition = screenPoint.dx - (labelRect.width / 2); + + final double tooltipRight = screenPoint.dx + (labelRect.width) / 2; + if (screenPoint.dy > (pointerLength + labelRect.height + padding) && + screenPoint.dy > 0) { + yPosition = + screenPoint.dy - labelRect.height - padding - pointerLength; + } else { + isBottom = true; + yPosition = (screenPoint.dy > 0 ? screenPoint.dy : 0) + + pointerLength + + padding; + } + + 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); + + final double labelOffsetX = + (labelRect.left + labelRect.width / 2) - textSize.width / 2; + final double labelOffsetY = + (labelRect.top + labelRect.height / 2) - textSize.height / 2; + labelOffset = Offset(labelOffsetX, labelOffsetY); + drawText(canvas, dataLabel, labelOffset, labelStyle); + } + } + + /// Method returns the trackball label + String _getTrackballLabel() { + final SparkChartPoint currentPoint = _rendererState._currentDataPoint; + String dataLabel = currentPoint.labelY; + final String labelX = currentPoint.labelX; + dataLabel = labelX != null ? labelX + ' : ' + dataLabel : dataLabel; + if (_trackball.tooltipFormatter != null) { + final TooltipFormatterDetails tooltipFormatterDetails = + 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); + } + + /// 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; + final Path path = Path(); + if (!isTop) { + if (isRight) { + path.moveTo(rect.right, rect.top + rect.height / 2 - pointerWidth); + path.lineTo(rect.right, rect.bottom - rect.height / 2 + pointerWidth); + path.lineTo(rect.right + pointerLength, screenPoint.dy); + path.lineTo(rect.right + pointerLength, screenPoint.dy); + path.lineTo(rect.right, rect.top + rect.height / 2 - pointerWidth); + } else { + path.moveTo(rect.left, rect.top + rect.height / 2 - pointerWidth); + path.lineTo(rect.left, rect.bottom - rect.height / 2 + pointerWidth); + path.lineTo(rect.left - pointerLength, screenPoint.dy); + path.lineTo(rect.left, rect.top + rect.height / 2 - pointerWidth); + } + } else { + final double yValue = isBottom ? rect.top : rect.bottom; + 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)); + path.lineTo(rect.left + rect.width / 2 - pointerWidth, yValue); + } + + final RRect roundedRect = RRect.fromRectAndCorners( + rect, + bottomLeft: borderRadius.bottomLeft, + bottomRight: borderRadius.bottomRight, + topLeft: borderRadius.topLeft, + topRight: borderRadius.topRight, + ); + + 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; + 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; + 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); + } else { + canvas.drawLine(point1, point2, paint); + } + } + + @override + bool shouldRepaint(TrackballPainter oldDelegate) => _isRepaint; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/enum.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/enum.dart new file mode 100644 index 000000000..d2e771bdf --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/enum.dart @@ -0,0 +1,93 @@ +/// Maps the index value. +typedef SparkChartIndexedValueMapper = R Function(int index); + +/// Maps the tooltip label value +typedef SparkChartTooltipCallback = String Function( + TooltipFormatterDetails details); + +/// Represents the marker display mode +enum SparkChartMarkerDisplayMode { + /// SparkChartMarkerDisplayMode.none does not allow to display markers on any side + none, + + /// SparkChartMarkerDisplayMode.all allows to display marker on all side + all, + + /// SparkChartMarkerDisplayMode.high allows to display marker on high point + high, + + /// SparkChartMarkerDisplayMode.low allows to display marker on low point + low, + + /// SparkChartMarkerDisplayMode.first allows to display marker on first point + first, + + /// SparkChartMarkerDisplayMode.last allows to display marker on last point + last +} + +/// Represents the marker shape +enum SparkChartMarkerShape { + /// SparkChartMarkerShape.circle displays marker in circular shape + circle, + + /// SparkChartMarkerShape.diamond displays marker in diamond shape + diamond, + + /// SparkChartMarkerShape.square displays marker in square shape + square, + + /// SparkChartMarkerShape.triangle displays marker in triangle shape + triangle, + + /// SparkChartMarkerShape.invertedTriangle displays marker in inverted triangle shape + invertedTriangle +} + +/// Represents the trackball spark chart activation mode +enum SparkChartActivationMode { + ///SparkChartActivationMode.tap allows to display the trackball on tap + tap, + + /// SparkChartActivationMode.doubleTap allows to display the trackball on double tap + doubleTap, + + /// SparkChartActivationMode.longPress allows to display the trckball on long press + longPress, +} + +/// Represents the label display mode +enum SparkChartLabelDisplayMode { + /// SparkChartLabelDisplayMode.none does not allow to display data labels on any side + none, + + ///SparkChartLabelDisplayMode.all allows to display data labels on all points + all, + + /// SparkChartLabelDisplayMode.high allows to display data on high point + high, + + /// SparkChartLabelDisplayMode.low allows to display marker on low point + low, + + /// SparkChartLabelDisplayMode.first allows to display marker on first point + first, + + /// SparkChartLabelDisplayMode.last allows to display marker on last point + last +} + +/// Specifies the tooltip formatter details +class TooltipFormatterDetails { + /// Creates the tooltip formatter details + TooltipFormatterDetails({this.x, this.y, this.label}); + + /// Specifies the x-value of data point + final dynamic x; + + /// Specifies the y-value of data point + final num y; + + /// Specifies the tooltip text + final String label; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart new file mode 100644 index 000000000..7ad512400 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart @@ -0,0 +1,812 @@ +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/foundation.dart'; +import '../marker.dart'; +import 'enum.dart'; + +/// Methods to get the 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; +} + +/// Measure the text and return the text size +Size getTextSize(String textValue, TextStyle textStyle) { + Size size; + final TextPainter textPainter = TextPainter( + 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)), + ); + textPainter.layout(); + size = Size(textPainter.width, textPainter.height); + return size; +} + +/// Draw the data label +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); + tp.layout(); + canvas.save(); + canvas.translate(point.dx, point.dy); + final Offset labelOffset = const Offset(0.0, 0.0); + tp.paint(canvas, labelOffset); + canvas.restore(); +} + +/// Returns lines count in a string +num _getMaxLinesContent(String text) { + return text != null && text.isNotEmpty && text.contains('\n') + ? text.split('\n').length + : 1; +} + +/// Draw the dashed line +void drawDashedPath( + Canvas canvas, Paint paint, 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); + + 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: DashArrayIntervalList(dashArray), + ), + paint); + } + } else { + canvas.drawPath(path, paint); + } +} + +/// To calculate dash array path for series +Path _dashPath( + Path source, { + @required DashArrayIntervalList 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; +} + +/// 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)); + 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); + return path; +} + +/// Returns the Inverted Triangle shape marker +Path drawInvertedTriangle(Path path, double x, double y, double size) { + path.moveTo(x + size / 2, y - size / 2); + + path.lineTo(x, y + size / 2); + path.lineTo(x - size / 2, y - size / 2); + path.lineTo(x + size / 2, y - size / 2); + path.close(); + return path; +} + +///Returns the Diamond shape marker +Path drawDiamond(Path path, double x, double y, double size) { + path.moveTo(x - size / 2, y); + path.lineTo(x, y + size / 2); + path.lineTo(x + size / 2, y); + path.lineTo(x, y - size / 2); + path.lineTo(x - size / 2, y); + path.close(); + return path; +} + +///Returns the Triangle shape marker +Path drawTriangle(Path path, double x, double y, double size) { + path.moveTo(x - size / 2, y + size / 2); + path.lineTo(x + size / 2, y + size / 2); + path.lineTo(x, y - size / 2); + path.lineTo(x - size / 2, y + size / 2); + path.close(); + return path; +} + +/// Method to find the sorted spark chart points +List sortSparkChartPoints(List dataPoints) { + final List sortedPoints = List.from(dataPoints); + sortedPoints.sort((SparkChartPoint firstPoint, SparkChartPoint secondPoint) { + firstPoint.x.compareTo(secondPoint.x); + if (firstPoint.x < secondPoint.x) { + return -1; + } else if (firstPoint.x > secondPoint.x) { + return 1; + } else { + return 0; + } + }); + + return sortedPoints; +} + +/// Method to find the sorted visible points +List sortScreenCoordiantePoints(List coordinatePoints) { + coordinatePoints.sort((Offset firstPoint, Offset secondPoint) { + firstPoint.dx.compareTo(secondPoint.dx); + if (firstPoint.dx < secondPoint.dx) { + return -1; + } else if (firstPoint.dx > secondPoint.dx) { + return 1; + } else { + return 0; + } + }); + + return 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, + 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); +} + +/// Calculates and return the spark chart layout size +Size getLayoutSize(BoxConstraints constraints) { + final double minHeight = 270; + final double minWidth = 480; + double height = constraints.maxHeight; + double width = constraints.maxWidth; + if (height == double.infinity) { + if (width != double.infinity) { + height = (width / 16) * 9; + } else { + height = minHeight; + } + } + + if (width == double.infinity) { + if (height != double.infinity) { + width = (height / 9) * 16; + } else { + width = minWidth; + } + } + + return Size(width, height); +} + +/// Represents the circular interval list +class DashArrayIntervalList { + /// Creates the circular interval list + DashArrayIntervalList(this._values); + + /// Specifies the list of value + final List _values; + + /// Specifies the index value + int _index = 0; + + /// Returns the value + T get next { + if (_index >= _values.length) { + _index = 0; + } + return _values[_index++]; + } +} + +/// Represents the spark chart point +class SparkChartPoint { + /// Creates the spark chart point + SparkChartPoint({this.x, this.y}); + + /// Specifies the x point + dynamic x; + + /// Specifies the y point + dynamic y; + + /// Specifes the pixel location of data label + + Offset dataLabelOffset; + + /// Specifies the x label + String labelX; + + /// Specifies the y label + String labelY; + + /// Specifies the actual x value + dynamic actualX; + + /// Specifies the color of that particular segment in bar series + Color color; +} + +/// Represents the spark chart data details +class SparkChartDataDetails { + /// Creates the spark chart container box + SparkChartDataDetails( + {this.data, this.dataCount, this.xValueMapper, this.yValueMapper}); + + /// Speficies the list of spark chart data + final List data; + + /// Specifies the spark chart data count + final int dataCount; + + /// Specifies the x-value mapper + final SparkChartIndexedValueMapper xValueMapper; + + /// Specifies the y-value mapper + final SparkChartIndexedValueMapper yValueMapper; +} + +/// Represents the spark chart container +class SparkChartContainer extends SingleChildRenderObjectWidget { + /// Creates the spark chart container + const SparkChartContainer({Widget child}) : super(child: child); + + @override + RenderObject createRenderObject(BuildContext context) { + return _SparKChartContainerBox(); + } +} + +/// Represents the spark chart container box +class _SparKChartContainerBox extends RenderShiftedBox { + _SparKChartContainerBox() : super(null); + + @override + void performLayout() { + size = getLayoutSize(constraints); + + child.layout( + BoxConstraints( + minHeight: 0.0, + maxHeight: size.height, + minWidth: 0.0, + maxWidth: size.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 + } + + @override + void paint(PaintingContext context, Offset offset) { + super.paint(context, offset); + } +} + +/// To draw the respective shapes for marker +Path getMarkerShapes( + SparkChartMarkerShape markerShape, Offset position, double size) { + final Path path = Path(); + switch (markerShape) { + case SparkChartMarkerShape.circle: + { + drawCircle(path, position.dx, position.dy, size); + } + break; + case SparkChartMarkerShape.square: + { + drawRectangle(path, position.dx, position.dy, size); + } + break; + + case SparkChartMarkerShape.invertedTriangle: + { + drawInvertedTriangle(path, position.dx, position.dy, size); + } + break; + + case SparkChartMarkerShape.diamond: + { + drawDiamond(path, position.dx, position.dy, size); + } + break; + case SparkChartMarkerShape.triangle: + { + drawTriangle(path, position.dx, position.dy, size); + } + break; + + default: + break; + } + + return path; +} + +/// 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, + ThemeData 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 SparkChartMarkerShape markerShape = marker.shape; + final double markerSize = marker.size; + final SparkChartMarkerDisplayMode markerDisplayMode = marker.displayMode; + final themeBasedColor = + themeData.brightness == Brightness.light ? Colors.white : Colors.black; + 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); + + final Offset firstMarkerOffset = Offset( + 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; + int i = type == 'Line' ? 0 : 1; + for (i = i; i < length; i++) { + fillPaint.color = marker.color ?? themeBasedColor; + if (i == (type == 'Line' ? 0 : 1)) { + firstPointColor != null + ? fillPaint.color = firstPointColor + : fillPaint.color = fillPaint.color; + } + + if (i == + (type == 'Line' + ? coordinatePoints.length - 1 + : coordinatePoints.length - 2)) { + lastPointColor != null + ? fillPaint.color = lastPointColor + : fillPaint.color = fillPaint.color; + } + + if (highPoint == coordinatePoints[i].dy) { + lowPointColor != null + ? fillPaint.color = lowPointColor + : fillPaint.color = fillPaint.color; + } + + if (lowPoint == coordinatePoints[i].dy) { + highPointColor != null + ? fillPaint.color = highPointColor + : fillPaint.color = fillPaint.color; + } + + if (negativePointColor != null && dataPoints[i].y < 0) { + fillPaint.color = negativePointColor; + } + + markerPath = getMarkerShapes( + markerShape, + Offset(offset.dx + coordinatePoints[i].dx, + offset.dy + coordinatePoints[i].dy), + markerSize); + canvas.drawPath(markerPath, fillPaint); + canvas.drawPath(markerPath, strokePaint); + } + } + break; + case SparkChartMarkerDisplayMode.first: + { + fillPaint.color = marker.color ?? themeBasedColor; + firstPointColor != null + ? fillPaint.color = firstPointColor + : fillPaint.color = fillPaint.color; + if (negativePointColor != null && + dataPoints[type == 'Line' ? 0 : 1].y < 0) { + fillPaint.color = negativePointColor; + } + markerPath = + getMarkerShapes(markerShape, firstMarkerOffset, markerSize); + canvas.drawPath(markerPath, fillPaint); + canvas.drawPath(markerPath, strokePaint); + } + break; + + case SparkChartMarkerDisplayMode.last: + { + fillPaint.color = marker.color ?? themeBasedColor; + lastPointColor != null + ? fillPaint.color = lastPointColor + : fillPaint.color = fillPaint.color; + if (negativePointColor != null && + dataPoints[type == 'Line' + ? coordinatePoints.length - 1 + : coordinatePoints.length - 2] + .y < + 0) { + fillPaint.color = negativePointColor; + } + markerPath = getMarkerShapes(markerShape, lastMarkerOffset, markerSize); + canvas.drawPath(markerPath, fillPaint); + canvas.drawPath(markerPath, strokePaint); + } + break; + + case SparkChartMarkerDisplayMode.low: + { + fillPaint.color = marker.color ?? themeBasedColor; + lowPointColor != null + ? fillPaint.color = lowPointColor + : fillPaint.color = fillPaint.color; + + 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 && + highPoint == coordinatePoints[j].dy && + dataPoints[j].y < 0) { + fillPaint.color = negativePointColor; + } + if (highPoint == coordinatePoints[j].dy) { + markerPath = getMarkerShapes( + markerShape, + Offset(offset.dx + coordinatePoints[j].dx, + offset.dy + coordinatePoints[j].dy), + markerSize); + canvas.drawPath(markerPath, fillPaint); + canvas.drawPath(markerPath, strokePaint); + } + } + } + break; + + case SparkChartMarkerDisplayMode.high: + { + fillPaint.color = marker.color ?? themeBasedColor; + highPointColor != null + ? fillPaint.color = highPointColor + : fillPaint.color = fillPaint.color; + + 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 && + lowPoint == coordinatePoints[j].dy && + dataPoints[j].y < 0) { + fillPaint.color = negativePointColor; + } + if (lowPoint == coordinatePoints[j].dy) { + markerPath = getMarkerShapes( + markerShape, + Offset(offset.dx + coordinatePoints[j].dx, + offset.dy + coordinatePoints[j].dy), + markerSize); + canvas.drawPath(markerPath, fillPaint); + canvas.drawPath(markerPath, strokePaint); + } + } + } + break; + + case SparkChartMarkerDisplayMode.none: + break; + } +} + +Color _getDataLabelSaturationColor( + Offset dataLabelOffset, + Offset coordinateOffset, + ThemeData 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; + } else if (type == 'Line') { + color = theme.brightness == Brightness.light + ? const Color.fromRGBO(255, 255, 255, 1) + : Colors.black; + } 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 + : dataLabelOffset.dy < (segment.top + offset.dy) + ? color = seriesColor + : color = theme.brightness == Brightness.light + ? const Color.fromRGBO(255, 255, 255, 1) + : Colors.black; + } + + color = getSaturationColor(color); + + return color; +} + +TextStyle _getTextStyle( + TextStyle labelStyle, + Offset dataLabelOffset, + Offset coordinateOffset, + Offset offset, + ThemeData 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( + 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, + ThemeData 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); + } + } + + break; + + 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); + } + 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); + + drawText( + 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 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); + } + } + } + + break; + + case SparkChartLabelDisplayMode.high: + { + 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); + } + } + } + + break; + + case SparkChartLabelDisplayMode.none: + break; + } +} diff --git a/packages/syncfusion_flutter_charts/pubspec.yaml b/packages/syncfusion_flutter_charts/pubspec.yaml index 022058d2c..fbcbe0a52 100644 --- a/packages/syncfusion_flutter_charts/pubspec.yaml +++ b/packages/syncfusion_flutter_charts/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_charts description: Syncfusion Flutter Charts is a data visualization library written natively in Dart for creating beautiful and high-performance cartesian and circular charts. -version: 18.3.35 +version: 18.3.40 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_charts environment: diff --git a/packages/syncfusion_flutter_core/lib/core.dart b/packages/syncfusion_flutter_core/lib/core.dart index f3aba2ef7..9f820a754 100644 --- a/packages/syncfusion_flutter_core/lib/core.dart +++ b/packages/syncfusion_flutter_core/lib/core.dart @@ -6,3 +6,4 @@ export './src/slider_controller.dart'; part './src/license.dart'; part './src/calendar/calendar_helper.dart'; +part './src/calendar/hijri_date_time.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 9920e5880..75c85eaa0 100644 --- a/packages/syncfusion_flutter_core/lib/src/calendar/calendar_helper.dart +++ b/packages/syncfusion_flutter_core/lib/src/calendar/calendar_helper.dart @@ -12,8 +12,8 @@ part of core; /// previous date time zone offset(+2). if both are not equal then calculate /// the difference (date.timeZoneOffset - currentDate.timeZoneOffset) /// and add the offset difference(2-1) to current date. -DateTime addDuration(DateTime date, Duration duration) { - DateTime currentDate = date.add(duration); +dynamic addDuration(dynamic date, Duration duration) { + dynamic currentDate = date.add(duration); if (date.timeZoneOffset != currentDate.timeZoneOffset) { currentDate = currentDate.add(date.timeZoneOffset - currentDate.timeZoneOffset); @@ -35,8 +35,8 @@ DateTime addDuration(DateTime date, Duration duration) { /// previous date time zone offset(+1). if both are not equal then calculate /// the difference date.timeZoneOffset - currentDate.timeZoneOffset) and add /// the offset difference(1-2) to current date. -DateTime subtractDuration(DateTime date, Duration duration) { - DateTime currentDate = date.subtract(duration); +dynamic subtractDuration(dynamic date, Duration duration) { + dynamic currentDate = date.subtract(duration); if (date.timeZoneOffset != currentDate.timeZoneOffset) { currentDate = currentDate.add(date.timeZoneOffset - currentDate.timeZoneOffset); @@ -46,14 +46,24 @@ DateTime subtractDuration(DateTime date, Duration duration) { } /// Returns the previous month start date for the given date. -DateTime getPreviousMonthDate(DateTime date) { +dynamic getPreviousMonthDate(dynamic date) { + if (date is HijriDateTime) { + return date.month == 1 + ? HijriDateTime(date.year - 1, 12, 01) + : HijriDateTime(date.year, date.month - 1, 1); + } return date.month == 1 ? DateTime(date.year - 1, 12, 1) : DateTime(date.year, date.month - 1, 1); } /// Returns the next month start date for the given date.. -DateTime getNextMonthDate(DateTime date) { +dynamic getNextMonthDate(dynamic date) { + if (date is HijriDateTime) { + return date.month == 12 + ? HijriDateTime(date.year + 1, 01, 01) + : HijriDateTime(date.year, date.month + 1, 1); + } return date.month == 12 ? DateTime(date.year + 1, 1, 1) : DateTime(date.year, date.month + 1, 1); @@ -62,7 +72,7 @@ DateTime getNextMonthDate(DateTime date) { /// Return the given date if the date in between first and last date /// else return first date/last date when the date before of first date or after /// last date -DateTime getValidDate(DateTime minDate, DateTime maxDate, DateTime date) { +dynamic getValidDate(dynamic minDate, dynamic maxDate, dynamic date) { if (date.isAfter(minDate)) { if (date.isBefore(maxDate)) { return date; @@ -75,7 +85,7 @@ DateTime getValidDate(DateTime minDate, DateTime maxDate, DateTime date) { } /// Check the dates are equal or not. -bool isSameDate(DateTime date1, DateTime date2) { +bool isSameDate(dynamic date1, dynamic date2) { if (date2 == date1) { return true; } @@ -84,20 +94,26 @@ bool isSameDate(DateTime date1, DateTime date2) { return false; } + if (date1 is HijriDateTime && date2 is HijriDateTime) { + return date1.month == date2.month && + date1.year == date2.year && + date1.day == date2.day && + date1._date == date2._date; + } + return date1.month == date2.month && date1.year == date2.year && date1.day == date2.day; } /// Check the date in between first and last date -bool isDateWithInDateRange( - DateTime startDate, DateTime endDate, DateTime date) { +bool isDateWithInDateRange(dynamic startDate, dynamic endDate, dynamic date) { if (startDate == null || endDate == null || date == null) { return false; } if (startDate.isAfter(endDate)) { - final DateTime temp = startDate; + final dynamic temp = startDate; startDate = endDate; endDate = temp; } @@ -110,27 +126,33 @@ bool isDateWithInDateRange( } /// Check the date before/same of last date -bool isSameOrBeforeDate(DateTime lastDate, DateTime date) { +bool isSameOrBeforeDate(dynamic lastDate, dynamic date) { return isSameDate(lastDate, date) || lastDate.isAfter(date); } /// Check the date after/same of first date -bool isSameOrAfterDate(DateTime firstDate, DateTime date) { +bool isSameOrAfterDate(dynamic firstDate, dynamic date) { return isSameDate(firstDate, date) || firstDate.isBefore(date); } /// Get the visible dates based on the date value and visible dates count. -List getVisibleDates(DateTime date, List nonWorkingDays, - int firstDayOfWeek, int visibleDatesCount) { - final List datesCollection = []; - DateTime currentDate = date; +List getVisibleDates(dynamic date, List nonWorkingDays, int firstDayOfWeek, + int visibleDatesCount) { + List datesCollection; + if (date is HijriDateTime) { + datesCollection = []; + } else { + datesCollection = []; + } + + dynamic currentDate = date; if (firstDayOfWeek != null) { currentDate = getFirstDayOfWeekDate(visibleDatesCount, date, firstDayOfWeek); } for (int i = 0; i < visibleDatesCount; i++) { - final DateTime visibleDate = addDuration(currentDate, Duration(days: i)); + final dynamic visibleDate = addDuration(currentDate, Duration(days: i)); if (nonWorkingDays != null && nonWorkingDays.contains(visibleDate.weekday)) { continue; @@ -144,16 +166,20 @@ List getVisibleDates(DateTime date, List nonWorkingDays, /// Calculate first day of week date value based original date with first day of /// week value. -DateTime getFirstDayOfWeekDate( - int visibleDatesCount, DateTime date, int firstDayOfWeek) { +dynamic getFirstDayOfWeekDate( + int visibleDatesCount, dynamic date, int firstDayOfWeek) { if (visibleDatesCount % 7 != 0) { return date; } const int numberOfWeekDays = 7; - DateTime currentDate = date; + dynamic currentDate = date; if (visibleDatesCount == 42) { - currentDate = DateTime(currentDate.year, currentDate.month, 1); + if (currentDate is HijriDateTime) { + currentDate = HijriDateTime(currentDate.year, currentDate.month, 1); + } else { + currentDate = DateTime(currentDate.year, currentDate.month, 1); + } } int value = -currentDate.weekday + firstDayOfWeek - numberOfWeekDays; 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 de9b5919d..c89ce3c23 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 @@ -51,32 +51,18 @@ class CustomScrollViewerLayout extends MultiChildRenderObjectWidget { final int _currentChildIndex; @override - RenderObject createRenderObject(BuildContext context) { + _CustomScrollViewLayout createRenderObject(BuildContext context) { return _CustomScrollViewLayout( _navigationDirection, _position, _currentChildIndex); } @override - void updateRenderObject(BuildContext context, RenderObject renderObject) { - final _CustomScrollViewLayout panel = context.findRenderObject(); - if (panel._navigationDirection != _navigationDirection) { - panel._navigationDirection = _navigationDirection; - } - - if (panel._position != null && _position == null) { - panel._position = null; - panel.markNeedsLayout(); - } - - if (panel._currentChildIndex != _currentChildIndex) { - panel._currentChildIndex = _currentChildIndex; - panel.markNeedsLayout(); - } - - if (panel._position != _position) { - panel._position = _position; - panel.markNeedsLayout(); - } + void updateRenderObject( + BuildContext context, _CustomScrollViewLayout renderObject) { + renderObject + ..position = _position + ..navigationDirection = _navigationDirection + ..currentChildIndex = _currentChildIndex; } } @@ -86,12 +72,46 @@ class _CustomScrollViewLayout extends RenderWrap { CustomScrollDirection _navigationDirection; + CustomScrollDirection get navigationDirection => _navigationDirection; + + set navigationDirection(CustomScrollDirection value) { + if (_navigationDirection == value) { + return; + } + + _navigationDirection = value; + } + // holds the index of the current displaying view int _currentChildIndex; + int get currentChildIndex => _currentChildIndex; + + set currentChildIndex(int value) { + if (_currentChildIndex == value) { + return; + } + + _currentChildIndex = value; + _position = 0; + _updateChild(); + markNeedsLayout(); + } + // _position contains distance that the view swiped double _position; + double get position => _position; + + set position(double value) { + if (_position == value) { + return; + } + + _position = value; + markNeedsLayout(); + } + // used to position the children on the panel on swiping. dynamic _currentChild, _firstChild, _lastChild; @@ -128,7 +148,7 @@ class _CustomScrollViewLayout extends RenderWrap { lastChildXPos = 0; } - //// sets the position as zero to restrict the view update when the view refreshed without swiping the view + // sets the position as zero to restrict the view update when the view refreshed without swiping the view if (_position == width || _position == -width) { if (_currentChild.parentData.offset.dx == width) { _position = 0; 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 new file mode 100644 index 000000000..f70c3ab57 --- /dev/null +++ b/packages/syncfusion_flutter_core/lib/src/calendar/hijri_date_time.dart @@ -0,0 +1,2056 @@ +part of core; + +/// Umm al-quara based array, to convert the date to hijri date time. +const List _kDateCollection = [ + 28607, + 28636, + 28665, + 28695, + 28724, + 28754, + 28783, + 28813, + 28843, + 28872, + 28901, + 28931, + 28960, + 28990, + 29019, + 29049, + 29078, + 29108, + 29137, + 29167, + 29196, + 29226, + 29255, + 29285, + 29315, + 29345, + 29375, + 29404, + 29434, + 29463, + 29492, + 29522, + 29551, + 29580, + 29610, + 29640, + 29669, + 29699, + 29729, + 29759, + 29788, + 29818, + 29847, + 29876, + 29906, + 29935, + 29964, + 29994, + 30023, + 30053, + 30082, + 30112, + 30141, + 30171, + 30200, + 30230, + 30259, + 30289, + 30318, + 30348, + 30378, + 30408, + 30437, + 30467, + 30496, + 30526, + 30555, + 30585, + 30614, + 30644, + 30673, + 30703, + 30732, + 30762, + 30791, + 30821, + 30850, + 30880, + 30909, + 30939, + 30968, + 30998, + 31027, + 31057, + 31086, + 31116, + 31145, + 31175, + 31204, + 31234, + 31263, + 31293, + 31322, + 31352, + 31381, + 31411, + 31441, + 31471, + 31500, + 31530, + 31559, + 31589, + 31618, + 31648, + 31676, + 31706, + 31736, + 31766, + 31795, + 31825, + 31854, + 31884, + 31913, + 31943, + 31972, + 32002, + 32031, + 32061, + 32090, + 32120, + 32150, + 32180, + 32209, + 32239, + 32268, + 32298, + 32327, + 32357, + 32386, + 32416, + 32445, + 32475, + 32504, + 32534, + 32563, + 32593, + 32622, + 32652, + 32681, + 32711, + 32740, + 32770, + 32799, + 32829, + 32858, + 32888, + 32917, + 32947, + 32976, + 33006, + 33035, + 33065, + 33094, + 33124, + 33153, + 33183, + 33213, + 33243, + 33272, + 33302, + 33331, + 33361, + 33390, + 33420, + 33450, + 33479, + 33509, + 33539, + 33568, + 33598, + 33627, + 33657, + 33686, + 33716, + 33745, + 33775, + 33804, + 33834, + 33863, + 33893, + 33922, + 33952, + 33981, + 34011, + 34040, + 34069, + 34099, + 34128, + 34158, + 34187, + 34217, + 34247, + 34277, + 34306, + 34336, + 34365, + 34395, + 34424, + 34454, + 34483, + 34512, + 34542, + 34571, + 34601, + 34631, + 34660, + 34690, + 34719, + 34749, + 34778, + 34808, + 34837, + 34867, + 34896, + 34926, + 34955, + 34985, + 35015, + 35044, + 35074, + 35103, + 35133, + 35162, + 35192, + 35222, + 35251, + 35280, + 35310, + 35340, + 35370, + 35399, + 35429, + 35458, + 35488, + 35517, + 35547, + 35576, + 35605, + 35635, + 35665, + 35694, + 35723, + 35753, + 35782, + 35811, + 35841, + 35871, + 35901, + 35930, + 35960, + 35989, + 36019, + 36048, + 36078, + 36107, + 36136, + 36166, + 36195, + 36225, + 36254, + 36284, + 36314, + 36343, + 36373, + 36403, + 36433, + 36462, + 36492, + 36521, + 36551, + 36580, + 36610, + 36639, + 36669, + 36698, + 36728, + 36757, + 36786, + 36816, + 36845, + 36875, + 36904, + 36934, + 36963, + 36993, + 37022, + 37052, + 37081, + 37111, + 37141, + 37170, + 37200, + 37229, + 37259, + 37288, + 37318, + 37347, + 37377, + 37406, + 37436, + 37465, + 37495, + 37524, + 37554, + 37584, + 37613, + 37643, + 37672, + 37701, + 37731, + 37760, + 37790, + 37819, + 37849, + 37878, + 37908, + 37938, + 37967, + 37997, + 38027, + 38056, + 38085, + 38115, + 38144, + 38174, + 38203, + 38233, + 38262, + 38292, + 38322, + 38351, + 38381, + 38410, + 38440, + 38469, + 38499, + 38528, + 38558, + 38587, + 38617, + 38646, + 38676, + 38705, + 38735, + 38764, + 38794, + 38823, + 38853, + 38882, + 38912, + 38941, + 38971, + 39001, + 39030, + 39059, + 39089, + 39118, + 39148, + 39178, + 39208, + 39237, + 39267, + 39297, + 39326, + 39355, + 39385, + 39414, + 39444, + 39473, + 39503, + 39532, + 39562, + 39592, + 39621, + 39650, + 39680, + 39709, + 39739, + 39768, + 39798, + 39827, + 39857, + 39886, + 39916, + 39946, + 39975, + 40005, + 40035, + 40064, + 40094, + 40123, + 40153, + 40182, + 40212, + 40241, + 40271, + 40300, + 40330, + 40359, + 40389, + 40418, + 40448, + 40477, + 40507, + 40536, + 40566, + 40595, + 40625, + 40655, + 40685, + 40714, + 40744, + 40773, + 40803, + 40832, + 40862, + 40892, + 40921, + 40951, + 40980, + 41009, + 41039, + 41068, + 41098, + 41127, + 41157, + 41186, + 41216, + 41245, + 41275, + 41304, + 41334, + 41364, + 41393, + 41422, + 41452, + 41481, + 41511, + 41540, + 41570, + 41599, + 41629, + 41658, + 41688, + 41718, + 41748, + 41777, + 41807, + 41836, + 41865, + 41894, + 41924, + 41953, + 41983, + 42012, + 42042, + 42072, + 42102, + 42131, + 42161, + 42190, + 42220, + 42249, + 42279, + 42308, + 42337, + 42367, + 42397, + 42426, + 42456, + 42485, + 42515, + 42545, + 42574, + 42604, + 42633, + 42662, + 42692, + 42721, + 42751, + 42780, + 42810, + 42839, + 42869, + 42899, + 42929, + 42958, + 42988, + 43017, + 43046, + 43076, + 43105, + 43135, + 43164, + 43194, + 43223, + 43253, + 43283, + 43312, + 43342, + 43371, + 43401, + 43430, + 43460, + 43489, + 43519, + 43548, + 43578, + 43607, + 43637, + 43666, + 43696, + 43726, + 43755, + 43785, + 43814, + 43844, + 43873, + 43903, + 43932, + 43962, + 43991, + 44021, + 44050, + 44080, + 44109, + 44139, + 44169, + 44198, + 44228, + 44258, + 44287, + 44317, + 44346, + 44375, + 44405, + 44434, + 44464, + 44493, + 44523, + 44553, + 44582, + 44612, + 44641, + 44671, + 44700, + 44730, + 44759, + 44788, + 44818, + 44847, + 44877, + 44906, + 44936, + 44966, + 44996, + 45025, + 45055, + 45084, + 45114, + 45143, + 45172, + 45202, + 45231, + 45261, + 45290, + 45320, + 45350, + 45380, + 45409, + 45439, + 45468, + 45498, + 45527, + 45556, + 45586, + 45615, + 45644, + 45674, + 45704, + 45733, + 45763, + 45793, + 45823, + 45852, + 45882, + 45911, + 45940, + 45970, + 45999, + 46028, + 46058, + 46088, + 46117, + 46147, + 46177, + 46206, + 46236, + 46265, + 46295, + 46324, + 46354, + 46383, + 46413, + 46442, + 46472, + 46501, + 46531, + 46560, + 46590, + 46620, + 46649, + 46679, + 46708, + 46738, + 46767, + 46797, + 46826, + 46856, + 46885, + 46915, + 46944, + 46974, + 47003, + 47033, + 47063, + 47092, + 47122, + 47151, + 47181, + 47210, + 47240, + 47269, + 47298, + 47328, + 47357, + 47387, + 47417, + 47446, + 47476, + 47506, + 47535, + 47565, + 47594, + 47624, + 47653, + 47682, + 47712, + 47741, + 47771, + 47800, + 47830, + 47860, + 47890, + 47919, + 47949, + 47978, + 48008, + 48037, + 48066, + 48096, + 48125, + 48155, + 48184, + 48214, + 48244, + 48273, + 48303, + 48333, + 48362, + 48392, + 48421, + 48450, + 48480, + 48509, + 48538, + 48568, + 48598, + 48627, + 48657, + 48687, + 48717, + 48746, + 48776, + 48805, + 48834, + 48864, + 48893, + 48922, + 48952, + 48982, + 49011, + 49041, + 49071, + 49100, + 49130, + 49160, + 49189, + 49218, + 49248, + 49277, + 49306, + 49336, + 49365, + 49395, + 49425, + 49455, + 49484, + 49514, + 49543, + 49573, + 49602, + 49632, + 49661, + 49690, + 49720, + 49749, + 49779, + 49809, + 49838, + 49868, + 49898, + 49927, + 49957, + 49986, + 50016, + 50045, + 50075, + 50104, + 50133, + 50163, + 50192, + 50222, + 50252, + 50281, + 50311, + 50340, + 50370, + 50400, + 50429, + 50459, + 50488, + 50518, + 50547, + 50576, + 50606, + 50635, + 50665, + 50694, + 50724, + 50754, + 50784, + 50813, + 50843, + 50872, + 50902, + 50931, + 50960, + 50990, + 51019, + 51049, + 51078, + 51108, + 51138, + 51167, + 51197, + 51227, + 51256, + 51286, + 51315, + 51345, + 51374, + 51403, + 51433, + 51462, + 51492, + 51522, + 51552, + 51582, + 51611, + 51641, + 51670, + 51699, + 51729, + 51758, + 51787, + 51816, + 51846, + 51876, + 51906, + 51936, + 51965, + 51995, + 52025, + 52054, + 52083, + 52113, + 52142, + 52171, + 52200, + 52230, + 52260, + 52290, + 52319, + 52349, + 52379, + 52408, + 52438, + 52467, + 52497, + 52526, + 52555, + 52585, + 52614, + 52644, + 52673, + 52703, + 52733, + 52762, + 52792, + 52822, + 52851, + 52881, + 52910, + 52939, + 52969, + 52998, + 53028, + 53057, + 53087, + 53116, + 53146, + 53176, + 53205, + 53235, + 53264, + 53294, + 53324, + 53353, + 53383, + 53412, + 53441, + 53471, + 53500, + 53530, + 53559, + 53589, + 53619, + 53648, + 53678, + 53708, + 53737, + 53767, + 53796, + 53825, + 53855, + 53884, + 53913, + 53943, + 53973, + 54003, + 54032, + 54062, + 54092, + 54121, + 54151, + 54180, + 54209, + 54239, + 54268, + 54297, + 54327, + 54357, + 54387, + 54416, + 54446, + 54476, + 54505, + 54535, + 54564, + 54593, + 54623, + 54652, + 54681, + 54711, + 54741, + 54770, + 54800, + 54830, + 54859, + 54889, + 54919, + 54948, + 54977, + 55007, + 55036, + 55066, + 55095, + 55125, + 55154, + 55184, + 55213, + 55243, + 55273, + 55302, + 55332, + 55361, + 55391, + 55420, + 55450, + 55479, + 55508, + 55538, + 55567, + 55597, + 55627, + 55657, + 55686, + 55716, + 55745, + 55775, + 55804, + 55834, + 55863, + 55892, + 55922, + 55951, + 55981, + 56011, + 56040, + 56070, + 56100, + 56129, + 56159, + 56188, + 56218, + 56247, + 56276, + 56306, + 56335, + 56365, + 56394, + 56424, + 56454, + 56483, + 56513, + 56543, + 56572, + 56601, + 56631, + 56660, + 56690, + 56719, + 56749, + 56778, + 56808, + 56837, + 56867, + 56897, + 56926, + 56956, + 56985, + 57015, + 57044, + 57074, + 57103, + 57133, + 57162, + 57192, + 57221, + 57251, + 57280, + 57310, + 57340, + 57369, + 57399, + 57429, + 57458, + 57487, + 57517, + 57546, + 57576, + 57605, + 57634, + 57664, + 57694, + 57723, + 57753, + 57783, + 57813, + 57842, + 57871, + 57901, + 57930, + 57959, + 57989, + 58018, + 58048, + 58077, + 58107, + 58137, + 58167, + 58196, + 58226, + 58255, + 58285, + 58314, + 58343, + 58373, + 58402, + 58432, + 58461, + 58491, + 58521, + 58551, + 58580, + 58610, + 58639, + 58669, + 58698, + 58727, + 58757, + 58786, + 58816, + 58845, + 58875, + 58905, + 58934, + 58964, + 58994, + 59023, + 59053, + 59082, + 59111, + 59141, + 59170, + 59200, + 59229, + 59259, + 59288, + 59318, + 59348, + 59377, + 59407, + 59436, + 59466, + 59495, + 59525, + 59554, + 59584, + 59613, + 59643, + 59672, + 59702, + 59731, + 59761, + 59791, + 59820, + 59850, + 59879, + 59909, + 59939, + 59968, + 59997, + 60027, + 60056, + 60086, + 60115, + 60145, + 60174, + 60204, + 60234, + 60264, + 60293, + 60323, + 60352, + 60381, + 60411, + 60440, + 60469, + 60499, + 60528, + 60558, + 60588, + 60618, + 60648, + 60677, + 60707, + 60736, + 60765, + 60795, + 60824, + 60853, + 60883, + 60912, + 60942, + 60972, + 61002, + 61031, + 61061, + 61090, + 61120, + 61149, + 61179, + 61208, + 61237, + 61267, + 61296, + 61326, + 61356, + 61385, + 61415, + 61445, + 61474, + 61504, + 61533, + 61563, + 61592, + 61621, + 61651, + 61680, + 61710, + 61739, + 61769, + 61799, + 61828, + 61858, + 61888, + 61917, + 61947, + 61976, + 62006, + 62035, + 62064, + 62094, + 62123, + 62153, + 62182, + 62212, + 62242, + 62271, + 62301, + 62331, + 62360, + 62390, + 62419, + 62448, + 62478, + 62507, + 62537, + 62566, + 62596, + 62625, + 62655, + 62685, + 62715, + 62744, + 62774, + 62803, + 62832, + 62862, + 62891, + 62921, + 62950, + 62980, + 63009, + 63039, + 63069, + 63099, + 63128, + 63157, + 63187, + 63216, + 63246, + 63275, + 63305, + 63334, + 63363, + 63393, + 63423, + 63453, + 63482, + 63512, + 63541, + 63571, + 63600, + 63630, + 63659, + 63689, + 63718, + 63747, + 63777, + 63807, + 63836, + 63866, + 63895, + 63925, + 63955, + 63984, + 64014, + 64043, + 64073, + 64102, + 64131, + 64161, + 64190, + 64220, + 64249, + 64279, + 64309, + 64339, + 64368, + 64398, + 64427, + 64457, + 64486, + 64515, + 64545, + 64574, + 64603, + 64633, + 64663, + 64692, + 64722, + 64752, + 64782, + 64811, + 64841, + 64870, + 64899, + 64929, + 64958, + 64987, + 65017, + 65047, + 65076, + 65106, + 65136, + 65166, + 65195, + 65225, + 65254, + 65283, + 65313, + 65342, + 65371, + 65401, + 65431, + 65460, + 65490, + 65520, + 65549, + 65579, + 65608, + 65638, + 65667, + 65697, + 65726, + 65755, + 65785, + 65815, + 65844, + 65874, + 65903, + 65933, + 65963, + 65992, + 66022, + 66051, + 66081, + 66110, + 66140, + 66169, + 66199, + 66228, + 66258, + 66287, + 66317, + 66346, + 66376, + 66405, + 66435, + 66465, + 66494, + 66524, + 66553, + 66583, + 66612, + 66641, + 66671, + 66700, + 66730, + 66760, + 66789, + 66819, + 66849, + 66878, + 66908, + 66937, + 66967, + 66996, + 67025, + 67055, + 67084, + 67114, + 67143, + 67173, + 67203, + 67233, + 67262, + 67292, + 67321, + 67351, + 67380, + 67409, + 67439, + 67468, + 67497, + 67527, + 67557, + 67587, + 67617, + 67646, + 67676, + 67705, + 67735, + 67764, + 67793, + 67823, + 67852, + 67882, + 67911, + 67941, + 67971, + 68000, + 68030, + 68060, + 68089, + 68119, + 68148, + 68177, + 68207, + 68236, + 68266, + 68295, + 68325, + 68354, + 68384, + 68414, + 68443, + 68473, + 68502, + 68532, + 68561, + 68591, + 68620, + 68650, + 68679, + 68708, + 68738, + 68768, + 68797, + 68827, + 68857, + 68886, + 68916, + 68946, + 68975, + 69004, + 69034, + 69063, + 69092, + 69122, + 69152, + 69181, + 69211, + 69240, + 69270, + 69300, + 69330, + 69359, + 69388, + 69418, + 69447, + 69476, + 69506, + 69535, + 69565, + 69595, + 69624, + 69654, + 69684, + 69713, + 69743, + 69772, + 69802, + 69831, + 69861, + 69890, + 69919, + 69949, + 69978, + 70008, + 70038, + 70067, + 70097, + 70126, + 70156, + 70186, + 70215, + 70245, + 70274, + 70303, + 70333, + 70362, + 70392, + 70421, + 70451, + 70481, + 70510, + 70540, + 70570, + 70599, + 70629, + 70658, + 70687, + 70717, + 70746, + 70776, + 70805, + 70835, + 70864, + 70894, + 70924, + 70954, + 70983, + 71013, + 71042, + 71071, + 71101, + 71130, + 71159, + 71189, + 71218, + 71248, + 71278, + 71308, + 71337, + 71367, + 71397, + 71426, + 71455, + 71485, + 71514, + 71543, + 71573, + 71602, + 71632, + 71662, + 71691, + 71721, + 71751, + 71781, + 71810, + 71839, + 71869, + 71898, + 71927, + 71957, + 71986, + 72016, + 72046, + 72075, + 72105, + 72135, + 72164, + 72194, + 72223, + 72253, + 72282, + 72311, + 72341, + 72370, + 72400, + 72429, + 72459, + 72489, + 72518, + 72548, + 72577, + 72607, + 72637, + 72666, + 72695, + 72725, + 72754, + 72784, + 72813, + 72843, + 72872, + 72902, + 72931, + 72961, + 72991, + 73020, + 73050, + 73080, + 73109, + 73139, + 73168, + 73197, + 73227, + 73256, + 73286, + 73315, + 73345, + 73375, + 73404, + 73434, + 73464, + 73493, + 73523, + 73552, + 73581, + 73611, + 73640, + 73669, + 73699, + 73729, + 73758, + 73788, + 73818, + 73848, + 73877, + 73907, + 73936, + 73965, + 73995, + 74024, + 74053, + 74083, + 74113, + 74142, + 74172, + 74202, + 74231, + 74261, + 74291, + 74320, + 74349, + 74379, + 74408, + 74437, + 74467, + 74497, + 74526, + 74556, + 74586, + 74615, + 74645, + 74675, + 74704, + 74733, + 74763, + 74792, + 74822, + 74851, + 74881, + 74910, + 74940, + 74969, + 74999, + 75029, + 75058, + 75088, + 75117, + 75147, + 75176, + 75206, + 75235, + 75264, + 75294, + 75323, + 75353, + 75383, + 75412, + 75442, + 75472, + 75501, + 75531, + 75560, + 75590, + 75619, + 75648, + 75678, + 75707, + 75737, + 75766, + 75796, + 75826, + 75856, + 75885, + 75915, + 75944, + 75974, + 76003, + 76032, + 76062, + 76091, + 76121, + 76150, + 76180, + 76210, + 76239, + 76269, + 76299, + 76328, + 76358, + 76387, + 76416, + 76446, + 76475, + 76505, + 76534, + 76564, + 76593, + 76623, + 76653, + 76682, + 76712, + 76741, + 76771, + 76801, + 76830, + 76859, + 76889, + 76918, + 76948, + 76977, + 77007, + 77036, + 77066, + 77096, + 77125, + 77155, + 77185, + 77214, + 77243, + 77273, + 77302, + 77332, + 77361, + 77390, + 77420, + 77450, + 77479, + 77509, + 77539, + 77569, + 77598, + 77627, + 77657, + 77686, + 77715, + 77745, + 77774, + 77804, + 77833, + 77863, + 77893, + 77923, + 77952, + 77982, + 78011, + 78041, + 78070, + 78099, + 78129, + 78158, + 78188, + 78217, + 78247, + 78277, + 78307, + 78336, + 78366, + 78395, + 78425, + 78454, + 78483, + 78513, + 78542, + 78572, + 78601, + 78631, + 78661, + 78690, + 78720, + 78750, + 78779, + 78808, + 78838, + 78867, + 78897, + 78926, + 78956, + 78985, + 79015, + 79044, + 79074, + 79104, + 79133, + 79163, + 79192, + 79222, + 79251, + 79281, + 79310, + 79340, + 79369, + 79399, + 79428, + 79458, + 79487, + 79517, + 79546, + 79576, + 79606, + 79635, + 79665, + 79695, + 79724, + 79753, + 79783, + 79812, + 79841, + 79871, + 79900, + 79930, + 79960, + 79990 +]; + +/// An HijriDateTime data. +/// +/// An object contains the properties about the date time along with hijri data. +/// +/// ```dart +/// final HijriDateTime date = HijriDateTime(1442, 02, 10); +/// +/// ``` +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); + } + + /// returns the hijri date value based on the current date + /// + /// ```dart + /// + /// final HijriDateTime currentDate = HijriDateTime.now(); + /// + /// ``` + static HijriDateTime now() { + final DateTime today = DateTime.now(); + return convertToHijriDate(today); + } + + /// The gregorian date value for [this] + DateTime _date; + + /// returns the hijri date from the given date + /// + /// ```dart + /// + /// final HijriDateTime currentDate = HijriDateTime.fromDateTime( + /// DateTime.now)); + /// + /// ``` + static HijriDateTime fromDateTime(DateTime date) { + return convertToHijriDate(date); + } + + /// Returns the gregorian date value of [this] value. + /// + /// ``` + /// + /// final HijriDateTime currentDate = HijriDateTime.now(); + /// final DateTime date = currentDate.toDateTime(); + /// + /// ``` + DateTime toDateTime() { + return _date; + } + + /// Defines the hijri year value. + final int year; + + /// Defines the hijri month value from {1 ... 12}. + final int month; + + /// Defines the hijri day value. + final int day; + + /// The weekday for the hijri date. + int get weekday => _date.weekday; + + /// The time zone offset for the hijri date. + Duration get timeZoneOffset => _date.timeZoneOffset; + + /// Returns true if [this] occurs after [other]. + /// + /// The comparison is independent of whether the time is in UTC or in the + /// local time zone. + bool isAfter(HijriDateTime other) { + return _date.millisecondsSinceEpoch > other._date.millisecondsSinceEpoch; + } + + /// Returns true if [this] occurs at the same moment as [other]. + /// + /// The comparison is independent of whether the time is in UTC or in the local + /// time zone. + bool isBefore(HijriDateTime other) { + return _date.millisecondsSinceEpoch < other._date.millisecondsSinceEpoch; + } + + /// Returns true if [this] occurs at the same moment as [other]. + /// + /// The comparison is independent of whether the time is in UTC or in the local + /// time zone. + bool isAtSameMomentAs(HijriDateTime other) { + return _date.millisecondsSinceEpoch == other._date.millisecondsSinceEpoch; + } + + /// Returns a [Duration] with the difference when subtracting [other] from + /// [this]. + /// + /// The returned [Duration] will be negative if [other] occurs after [this]. + Duration difference(HijriDateTime other) { + return _date.difference(other._date); + } + + /// Compares this HijriDateTime object to [other], + /// returning zero if the values are equal. + /// + /// Returns a negative value if this HijriDateTime [isBefore] [other]. It + /// returns 0 if it [isAtSameMomentAs] [other], and returns a positive value + /// otherwise (when this [isAfter] [other]). + int compareTo(HijriDateTime other) { + return _date.compareTo(other._date); + } + + @override + String toString() { + return year.toString() + '-' + _twoDigits(month) + '-' + _twoDigits(day); + } + + String _twoDigits(int n) { + if (n >= 10) return '$n'; + return '0$n'; + } + + /// Returns a new [HijriDateTime] instance with [duration] added to [this]. + HijriDateTime add(Duration duration) { + return _add(duration); + } + + /// returns the previous date from the given value. + HijriDateTime _getPreviousDate(HijriDateTime date, int day) { + if (day <= 0) { + date = getPreviousMonthDate(date); + final int monthLength = date.getNumberOfDatesInMonth(); + day = monthLength + day; + return _getPreviousDate(date, day); + } + + return HijriDateTime(date.year, date.month, day); + } + + /// Returns the next possible date from the given value. + HijriDateTime _getNextDate(int monthLength, HijriDateTime date, int day) { + if (day > monthLength) { + day -= monthLength; + date = getNextMonthDate(date); + monthLength = date.getNumberOfDatesInMonth(); + return _getNextDate(monthLength, date, day); + } + + return HijriDateTime(date.year, date.month, day); + } + + /// Returns new [HijriDateTime] instance by subtracting given [Duration]. + HijriDateTime _add(Duration duration) { + final int lengthOfMonth = getNumberOfDatesInMonth(); + int newDay, newMonth, newYear; + if (duration.inDays != null) { + HijriDateTime addedDate; + newDay = duration.inDays + day; + if (newDay > lengthOfMonth) { + addedDate = _getNextDate(lengthOfMonth, this, newDay); + } else if (newDay <= 0) { + addedDate = _getPreviousDate(this, newDay); + } + + if (addedDate != null) { + return addedDate; + } + } + + return HijriDateTime(newYear ?? year, newMonth ?? month, newDay ?? day); + } + + /// Returns a new [HijriDateTime] instance with [duration] subtracted to + /// [this]. + HijriDateTime subtract(Duration duration) { + return _add(-duration); + } + + /// returns the number of dates in the month + int getNumberOfDatesInMonth() { + final int totalYear = year - 1; + final int totalMonths = (totalYear * 12) + 1 + (month - 1); + final int i = totalMonths - 16260; + return _kDateCollection[i] - _kDateCollection[i - 1]; + } + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + final HijriDateTime otherStyle = other; + return otherStyle.month == month && + otherStyle.year == year && + otherStyle.day == day; + } + + @override + int get hashCode { + return hashValues(month, day, year); + } +} + +/// Converts and retusn the hijri date for the given gregorian date. +HijriDateTime convertToHijriDate(DateTime date) { + int day = date.day; + int month = date.month; + int year = date.year; + int tMonth = month; + int tYear = year; + if (tMonth < 3) { + tYear -= 1; + tMonth += 12; + } + int yPrefix = (tYear / 100).floor(); + int julianOffset = yPrefix - (yPrefix / 4.0).floor() - 2; + final int julianNumber = (365.25 * (tYear + 4716)).floor() + + (30.6001 * (tMonth + 1)).floor() + + day - + julianOffset - + 1524; + yPrefix = ((julianNumber - 1867216.25) / 36524.25).floor(); + julianOffset = yPrefix - (yPrefix / 4.0).floor() + 1; + final int b = julianNumber + julianOffset + 1524; + int c = ((b - 122.1) / 365.25).floor(); + final int d = (365.25 * c).floor(); + final int tempMonth = ((b - d) / 30.6001).floor(); + day = (b - d) - (30.6001 * tempMonth).floor(); + month = ((b - d) / 20.6001).floor(); + if (month > 13) { + c += 1; + month -= 12; + } + month -= 1; + year = c - 4716; + final int modifiedJulianDate = julianNumber - 2400000; + // date calculation for year after 2077 + final double iYear = 10631.0 / 30.0; + int z = julianNumber - 1948084; + final int cyc = (z / 10631.0).floor(); + z = z - 10631 * cyc; + final int j = ((z - 0.1335) / iYear).floor(); + final int iy = 30 * cyc + j; + z = z - (j * iYear + 0.1335).floor(); + int im = ((z + 28.5001) / 29.5).floor(); + /* istanbul ignore next */ + if (im == 13) { + im = 12; + } + + final int tempDay = z - (29.5001 * im - 29).floor(); + int i = 0; + for (; i < _kDateCollection.length; i++) { + if (_kDateCollection[i] > modifiedJulianDate) { + break; + } + } + final int iln = i + 16260; + final int ii = ((iln - 1) / 12).floor(); + int hYear = ii + 1; + int hMonth = iln - 12 * ii; + int hDate = modifiedJulianDate - _kDateCollection[i - 1] + 1; + if ((hDate.toString() + '').length > 2) { + hDate = tempDay; + hMonth = im; + hYear = iy; + } + + return HijriDateTime(hYear, hMonth, hDate); +} + +/// Converts and returns the gregorian date from the given hijri date values. +DateTime convertToGregorianDate(HijriDateTime date, + {int year, int month, int day}) { + if (date != null && date._date != null) { + return date._date; + } + + /// When the year exceeds the maximum year limit return the maximum value. + if (year > 1500) { + return DateTime(2077, 11, 16); + } + + final int iy = year; + final int im = month; + final int id = day; + final int ii = iy - 1; + final int iln = (ii * 12) + 1 + (im - 1); + final int i = iln - 16260; + final int mcjdn = id + _kDateCollection[i - 1] - 1; + final int julianDate = mcjdn + 2400000; + final int z = (julianDate + 0.5).floor(); + int a = ((z - 1867216.25) / 36524.25).floor(); + a = z + 1 + a - (a / 4).floor(); + final int b = a + 1524; + final int c = ((b - 122.1) / 365.25).floor(); + final int d = (365.25 * c).floor(); + final int e = ((b - d) / 30.6001).floor(); + final int gDay = b - d - (e * 30.6001).floor(); + int gMonth = e - (e > 13.5 ? 13 : 1); + final int gYear = c - (gMonth > 2.5 ? 4716 : 4715); + /* istanbul ignore next */ + if (gYear <= 0) { + gMonth--; + } // No year zero + return DateTime(gYear, gMonth, gDay); +} 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 b1c5db530..09fdd63a2 100644 --- a/packages/syncfusion_flutter_core/lib/src/localizations/global_localizations.dart +++ b/packages/syncfusion_flutter_core/lib/src/localizations/global_localizations.dart @@ -14,14 +14,10 @@ abstract class SfLocalizations { /// Label that is displayed when there are no events for a /// selected date in a calendar widget. - /// This label is displayed under agenda section in month view. + /// This label is displayed when there are no events on today date and display date in schedule view and + /// displayed under agenda section in month view. String get noEventsCalendarLabel; - /// Label that is displayed when there are no events on today date and - /// display date in a calendar widget. - /// This label is displayed in calendar schedule view. - String get scheduleViewNewEventLabel; - /// Label that is displayed in the calendar header view when allowed views /// have calendar day view. String get allowedViewDayLabel; @@ -62,6 +58,78 @@ abstract class SfLocalizations { /// date picker on header interaction. String get todayLabel; + /// The header string for the first month of hirji calendar + String get muharramLabel; + + /// The header string for the second month of hirji calendar + String get safarLabel; + + /// The header string for the third month of hirji calendar + String get rabi1Label; + + /// The header string for the fourth month of hirji calendar + String get rabi2Label; + + /// The header string for the fifth month of hirji calendar + String get jumada1Label; + + /// The header string for the sixth month of hirji calendar + String get jumada2Label; + + /// The header string for the seventh month of hirji calendar + String get rajabLabel; + + /// The header string for the eight month of hirji calendar + String get shaabanLabel; + + /// The header string for the ninth month of hirji calendar + String get ramadanLabel; + + /// The header string for the tenth month of hirji calendar + String get shawwalLabel; + + /// The header string for the eleventh month of hirji calendar + String get dhualqiLabel; + + /// The header string for the twelfth month of hirji calendar + String get dhualhiLabel; + + /// The header string for the first month of hirji calendar + String get shortMuharramLabel; + + /// The header string for the second month of hirji calendar + String get shortSafarLabel; + + /// The header string for the third month of hirji calendar + String get shortRabi1Label; + + /// The header string for the fourth month of hirji calendar + String get shortRabi2Label; + + /// The header string for the fifth month of hirji calendar + String get shortJumada1Label; + + /// The header string for the sixth month of hirji calendar + String get shortJumada2Label; + + /// The header string for the seventh month of hirji calendar + String get shortRajabLabel; + + /// The header string for the eight month of hirji calendar + String get shortShaabanLabel; + + /// The header string for the ninth month of hirji calendar + String get shortRamadanLabel; + + /// The header string for the tenth month of hirji calendar + String get shortShawwalLabel; + + /// The header string for the eleventh month of hirji calendar + String get shortDhualqiLabel; + + /// The header string for the twelfth month of hirji calendar + String get shortDhualhiLabel; + /// Label that is displayed in the information panel of DataPager to represent /// the currently selected page in number of pages. /// @@ -181,9 +249,6 @@ class _DefaultLocalizations implements SfLocalizations { @override String get noEventsCalendarLabel => 'No events'; - @override - String get scheduleViewNewEventLabel => 'Nothing planned. Tap to create.'; - @override String get allowedViewDayLabel => 'DAY'; @@ -214,6 +279,78 @@ class _DefaultLocalizations implements SfLocalizations { @override String get todayLabel => 'TODAY'; + @override + String get muharramLabel => 'Muharram'; + + @override + String get safarLabel => 'Safar'; + + @override + String get rabi1Label => 'Rabi\' al-awwal'; + + @override + String get rabi2Label => 'Rabi\' al-thani'; + + @override + String get jumada1Label => 'Jumada al-awwal'; + + @override + String get jumada2Label => 'Jumada al-thani'; + + @override + String get rajabLabel => 'Rajab'; + + @override + String get shaabanLabel => 'Sha\'aban'; + + @override + String get ramadanLabel => 'Ramadan'; + + @override + String get shawwalLabel => 'Shawwal'; + + @override + String get dhualqiLabel => 'Dhu al-Qi\'dah'; + + @override + String get dhualhiLabel => 'Dhu al-Hijjah'; + + @override + String get shortMuharramLabel => 'Muh.'; + + @override + String get shortSafarLabel => 'Saf.'; + + @override + String get shortRabi1Label => 'Rabi. I'; + + @override + String get shortRabi2Label => 'Rabi. II'; + + @override + String get shortJumada1Label => 'Jum. I'; + + @override + String get shortJumada2Label => 'Jum. II'; + + @override + String get shortRajabLabel => 'Raj.'; + + @override + String get shortShaabanLabel => 'Sha.'; + + @override + String get shortRamadanLabel => 'Ram.'; + + @override + String get shortShawwalLabel => 'Shaw.'; + + @override + String get shortDhualqiLabel => 'Dhu\'l-Q'; + + @override + String get shortDhualhiLabel => 'Dhu\'l-H'; + @override String get ofDataPagerLabel => 'of'; diff --git a/packages/syncfusion_flutter_core/lib/src/slider_controller.dart b/packages/syncfusion_flutter_core/lib/src/slider_controller.dart index ddffc50c7..b1f01c80f 100644 --- a/packages/syncfusion_flutter_core/lib/src/slider_controller.dart +++ b/packages/syncfusion_flutter_core/lib/src/slider_controller.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Coordinates between [SfRangeSelector] and the widget which listens to it. @@ -177,7 +178,7 @@ import 'package:flutter/material.dart'; /// } /// } /// ``` -class RangeController extends ChangeNotifier { +class RangeController extends DiagnosticableTree with ChangeNotifier { /// Creates a new instance of [RangeController]. /// /// The [start] represents the currently selected value of the range selector. @@ -242,4 +243,11 @@ class RangeController extends ChangeNotifier { /// It can be either [double] or [DateTime]. dynamic get previousEnd => _previousEnd; dynamic _previousEnd; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('start', start)); + properties.add(DiagnosticsProperty('end', end)); + } } 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 6b98f9597..cac846162 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart @@ -127,7 +127,13 @@ class SfDataGridThemeData with Diagnosticable { fontSize: 14, color: Colors.black87), backgroundColor: Color.fromRGBO(255, 255, 255, 1), - sortIconColor: Colors.black54) + sortIconColor: Colors.black54, + hoverColor: Color.fromRGBO(245, 245, 245, 1), + hoverTextStyle: TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Colors.black87)) : const DataGridHeaderCellStyle( textStyle: TextStyle( fontFamily: 'Roboto', @@ -135,7 +141,13 @@ class SfDataGridThemeData with Diagnosticable { fontSize: 14, color: Color.fromRGBO(255, 255, 255, 1)), backgroundColor: Color.fromRGBO(33, 33, 33, 1), - sortIconColor: Colors.white54); + sortIconColor: Colors.white54, + hoverColor: Color.fromRGBO(66, 66, 66, 1), + hoverTextStyle: TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1))); cellStyle ??= isLight ? const DataGridCellStyle( textStyle: TextStyle( @@ -551,19 +563,33 @@ class DataGridHeaderCellStyle extends DataGridCellStyle { /// Create a [DataGridHeaderCellStyle] that's used to configure /// a style for the header cells in [SfDataGrid]. const DataGridHeaderCellStyle( - {Color backgroundColor, TextStyle textStyle, this.sortIconColor}) + {Color backgroundColor, + TextStyle textStyle, + this.sortIconColor, + this.hoverColor, + this.hoverTextStyle}) : super(backgroundColor: backgroundColor, textStyle: textStyle); /// The color of the sort icon which indicates the ascending or descending /// order. final Color sortIconColor; + /// The background color of header cells when a pointer is hovering over it + /// in [SfDataGrid]. + final Color hoverColor; + + /// The style for text of header cells when a pointer is hovering over it + /// in [SfDataGrid]. + final TextStyle hoverTextStyle; + @override int get hashCode { final List values = [ textStyle, backgroundColor, - sortIconColor + sortIconColor, + hoverColor, + hoverTextStyle ]; return hashList(values); } @@ -579,7 +605,9 @@ class DataGridHeaderCellStyle extends DataGridCellStyle { return other is DataGridHeaderCellStyle && other.backgroundColor == backgroundColor && other.textStyle == textStyle && - other.sortIconColor == sortIconColor; + other.sortIconColor == sortIconColor && + other.hoverColor == hoverColor && + other.hoverTextStyle == hoverTextStyle; } /// Linearly interpolate between two styles. @@ -592,7 +620,9 @@ class DataGridHeaderCellStyle extends DataGridCellStyle { return DataGridHeaderCellStyle( backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), textStyle: TextStyle.lerp(a.textStyle, b.textStyle, t), - sortIconColor: Color.lerp(a.sortIconColor, b.sortIconColor, t)); + sortIconColor: Color.lerp(a.sortIconColor, b.sortIconColor, t), + hoverColor: Color.lerp(a.hoverColor, b.hoverColor, t), + hoverTextStyle: TextStyle.lerp(a.hoverTextStyle, b.hoverTextStyle, 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 de1efb304..ec0b90f1c 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/datapager_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/datapager_theme.dart @@ -83,19 +83,19 @@ class SfDataPagerThemeData with Diagnosticable { itemBorderRadius ??= BorderRadius.circular(50); + itemBorderColor ??= Colors.transparent; + selectedItemColor ??= Color.fromRGBO(33, 150, 243, 1); - selectedItemTextStyle = TextStyle( + selectedItemTextStyle ??= TextStyle( color: Color.fromRGBO(255, 255, 255, 1), fontSize: 14, fontFamily: 'Roboto', fontWeight: FontWeight.w400); - disabledItemColor ??= isLight - ? Color.fromRGBO(0, 0, 0, 0.36) - : Color.fromRGBO(255, 255, 255, 0.36); + disabledItemColor ??= Colors.transparent; - disabledItemTextStyle = TextStyle( + disabledItemTextStyle ??= TextStyle( color: isLight ? Color.fromRGBO(0, 0, 0, 0.36) : Color.fromRGBO(255, 255, 255, 0.36)); 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 8dd522a61..cd273b024 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/maps_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/maps_theme.dart @@ -152,7 +152,6 @@ class SfMapsThemeData with Diagnosticable { Color selectionColor, Color selectionStrokeColor, double selectionStrokeWidth, - TextStyle tooltipTextStyle, Color tooltipColor, Color tooltipStrokeColor, double tooltipStrokeWidth, @@ -217,7 +216,6 @@ class SfMapsThemeData with Diagnosticable { bubbleHoverStrokeWidth: bubbleHoverStrokeWidth, selectionColor: selectionColor, selectionStrokeColor: selectionStrokeColor, - tooltipTextStyle: tooltipTextStyle, tooltipColor: tooltipColor, tooltipStrokeColor: tooltipStrokeColor, tooltipStrokeWidth: tooltipStrokeWidth, @@ -261,7 +259,6 @@ class SfMapsThemeData with Diagnosticable { @required this.selectionColor, @required this.selectionStrokeColor, @required this.selectionStrokeWidth, - @required this.tooltipTextStyle, @required this.tooltipColor, @required this.tooltipStrokeColor, @required this.tooltipStrokeWidth, @@ -752,27 +749,6 @@ class SfMapsThemeData with Diagnosticable { /// ``` final double selectionStrokeWidth; - /// Specifies the textStyle for tooltip text. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// mapsThemeData: SfMapsThemeData( - /// tooltipTextStyle: TextStyle(color: Colors.black, - /// fontWeight: FontWeight.bold) - /// ) - /// ), - /// child: SfMaps(), - /// ), - /// ) - /// ); - /// } - /// ``` - final TextStyle tooltipTextStyle; - /// Specifies the fill color for tooltip. /// /// ```dart @@ -956,7 +932,6 @@ class SfMapsThemeData with Diagnosticable { Color selectionColor, Color selectionStrokeColor, double selectionStrokeWidth, - TextStyle tooltipTextStyle, Color tooltipColor, Color tooltipStrokeColor, double tooltipStrokeWidth, @@ -994,7 +969,6 @@ class SfMapsThemeData with Diagnosticable { selectionColor: selectionColor ?? this.selectionColor, selectionStrokeColor: selectionStrokeColor ?? this.selectionStrokeColor, selectionStrokeWidth: selectionStrokeWidth ?? this.selectionStrokeWidth, - tooltipTextStyle: tooltipTextStyle ?? this.tooltipTextStyle, tooltipColor: tooltipColor ?? this.tooltipColor, tooltipStrokeColor: tooltipStrokeColor ?? this.tooltipStrokeColor, tooltipStrokeWidth: tooltipStrokeWidth ?? this.tooltipStrokeWidth, @@ -1048,8 +1022,6 @@ class SfMapsThemeData with Diagnosticable { Color.lerp(a.selectionStrokeColor, b.selectionStrokeColor, t), selectionStrokeWidth: lerpDouble(a.selectionStrokeWidth, b.selectionStrokeWidth, t), - tooltipTextStyle: - TextStyle.lerp(a.tooltipTextStyle, b.tooltipTextStyle, t), tooltipColor: Color.lerp(a.tooltipColor, b.tooltipColor, t), tooltipStrokeColor: Color.lerp(a.tooltipStrokeColor, b.tooltipStrokeColor, t), @@ -1095,7 +1067,6 @@ class SfMapsThemeData with Diagnosticable { other.selectionColor == selectionColor && other.selectionStrokeColor == selectionStrokeColor && other.selectionStrokeWidth == selectionStrokeWidth && - other.tooltipTextStyle == tooltipTextStyle && other.tooltipColor == tooltipColor && other.tooltipStrokeColor == tooltipStrokeColor && other.tooltipStrokeWidth == tooltipStrokeWidth && @@ -1129,7 +1100,6 @@ class SfMapsThemeData with Diagnosticable { selectionColor, selectionStrokeColor, selectionStrokeWidth, - tooltipTextStyle, tooltipColor, tooltipStrokeColor, tooltipStrokeWidth, @@ -1196,9 +1166,6 @@ class SfMapsThemeData with Diagnosticable { defaultValue: defaultData.selectionStrokeColor)); properties.add(DoubleProperty('selectionStrokeWidth', selectionStrokeWidth, defaultValue: defaultData.selectionStrokeWidth)); - properties.add(DiagnosticsProperty( - 'tooltipTextStyle', tooltipTextStyle, - defaultValue: defaultData.tooltipTextStyle)); properties.add(ColorProperty('tooltipColor', tooltipColor, defaultValue: defaultData.tooltipColor)); properties.add(ColorProperty('tooltipStrokeColor', tooltipStrokeColor, diff --git a/packages/syncfusion_flutter_datagrid/CHANGELOG.md b/packages/syncfusion_flutter_datagrid/CHANGELOG.md index 78b97f27c..08daebe4a 100644 --- a/packages/syncfusion_flutter_datagrid/CHANGELOG.md +++ b/packages/syncfusion_flutter_datagrid/CHANGELOG.md @@ -1,3 +1,42 @@ +## Unreleased + +**Features** + +* Provided the support to show stacked headers i.e. unbound header rows. Unbound header rows span stacked header columns across multiple rows and columns. +* Provided the support to display an interactive view when the grid reaches its maximum offset while scrolling down. Tapping the interactive view triggers a callback to add more data from the data source of the grid at run time. +* Provided the support to highlight the header cells on mouse hover. +* Provided the callbacks support in SfDataPager to listen when page navigation is started and ended. +* Provided the support to set grid lines for header and stacked header cells. +* Provided the support to improve the compactness of the datagrid based on the visual density. + +**Breaking Changes** + +* All the properties in GridTextColumn, GridNumericColumn, GridDateTimeColumn and GridWidgetColumn classes are marked as final. So, these classes are immutable. + +## [18.3.53-beta] - 12/08/2020 + +**Bugs** + +* Now, the last row is considered to calculate the auto-fit column width. + +## [18.3.52-beta] - 12/01/2020 + +**Features** + +* Provided the support to show the scrollbars always and set the scrollphysics for vertical and horizontal scrollbars. + +## [18.3.50-beta] - 11/17/2020 + +**Features** + +* Provided the support to recalculate the column widths at run time. + +## [18.3.40-beta] - 10/13/2020 + +**Features** + +* Provided the support to apply custom sorting by overriding the `compare` method in `DataGridSource` class. + ## [18.3.35-beta] - 10/01/2020 **Features** @@ -16,6 +55,11 @@ * Provided the support to refresh the specific row's height at run time +**Breaking changes** + +* The `isHidden` property has been renamed as `visible` in the `GridColumn` class. +* The argument of `onQueryRowHeight` callback has been removed. Previously there was `height` parameter. Now, `RowHeightDetails` is passed as parameter which has `rowHeight` and `rowIndex` properties. + ## [18.2.59-beta] - 09/23/2020 No changes. @@ -65,4 +109,4 @@ Initial release. * Styling - Customize the appearance of cells and headers. Conditional styling is also supported. * Theme - Use a dark or light theme. * Accessibility - The DataGrid can easily be accessed by screen readers. -* Right to Left (RTL) - Right-to-left direction support for users working in RTL languages like Hebrew and Arabic. +* Right to Left (RTL) - Right-to-left direction support for users working in RTL languages like Hebrew and Arabic. \ No newline at end of file diff --git a/packages/syncfusion_flutter_datagrid/README.md b/packages/syncfusion_flutter_datagrid/README.md index 37d30ca72..449c9ef08 100644 --- a/packages/syncfusion_flutter_datagrid/README.md +++ b/packages/syncfusion_flutter_datagrid/README.md @@ -35,6 +35,10 @@ The Syncfusion Flutter DataGrid is used to display and manipulate data in a tabu * **Styling** - Customize the appearance of cells and headers. Conditional styling is also supported. +* **Stacked headers** - Show unbound header rows. Unbound header rows span stacked header columns across multiple rows and columns. + +* **Load more** - Display an interactive view when the grid reaches its maximum offset while scrolling down. Tapping the interactive view triggers a callback to add more data from the data source of the grid at run time. + * **Paging** - Load data in segments. It is useful when loading huge amounts of data. * **Theme** - Use a dark or light theme. diff --git a/packages/syncfusion_flutter_datagrid/lib/datagrid.dart b/packages/syncfusion_flutter_datagrid/lib/datagrid.dart index 4084aa473..6084ff055 100644 --- a/packages/syncfusion_flutter_datagrid/lib/datagrid.dart +++ b/packages/syncfusion_flutter_datagrid/lib/datagrid.dart @@ -10,6 +10,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart' as intl show DateFormat, NumberFormat; import 'package:flutter/foundation.dart' show kIsWeb; + import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; @@ -28,6 +29,7 @@ part './src/control/scrollable_panel/visual_container_helper.dart'; //Runtime part './src/control/runtime/grid_column.dart'; part './src/control/runtime/column_sizer.dart'; +part './src/control/runtime/stacked_header.dart'; //Generator part './src/control/generator/row_generator.dart'; @@ -35,6 +37,7 @@ part './src/control/generator/data_row.dart'; part './src/control/generator/data_row_base.dart'; part './src/control/generator/data_cell.dart'; part './src/control/generator/data_cell_base.dart'; +part './src/control/generator/spanned_data_row.dart'; //Helper part './src/control/helper/enums.dart'; @@ -57,6 +60,7 @@ part './src/cell_renderer/grid_header_cell_renderer.dart'; part './src/cell_renderer/grid_cell_numeric_field_renderer.dart'; part './src/cell_renderer/grid_cell_widget_renderer.dart'; part './src/cell_renderer/grid_cell_datetime_renderer.dart'; +part './src/cell_renderer/grid_cell_stacked_header_renderer.dart'; //Selection Controller part './src/control/selection_controller/row_selection_manager.dart'; @@ -69,9 +73,6 @@ part './src/control/selection_controller/current_cell_manager.dart'; part './src/grid_common/list_base.dart'; part './src/grid_common/list_generic_base.dart'; part './src/grid_common/math_helper.dart'; -part './src/grid_common/collections/generic_tree_table.dart'; -part './src/grid_common/collections/generic_tree_table_with_counter.dart'; -part './src/grid_common/collections/generic_tree_table_with_summary.dart'; part './src/grid_common/collections/tree_table.dart'; part './src/grid_common/collections/tree_table_with_counter.dart'; part './src/grid_common/collections/tree_table_with_summary.dart'; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_stacked_header_renderer.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_stacked_header_renderer.dart new file mode 100644 index 000000000..762cb4cce --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_stacked_header_renderer.dart @@ -0,0 +1,46 @@ +part of datagrid; + +/// A cell renderer which displays the header text in the +/// stacked columns of the stacked header rows. +class _GridStackedHeaderCellRenderer + extends GridVirtualizingCellRendererBase { + /// Creates the [GridStackedHeaderCellRenderer] for [SfDataGrid] widget. + _GridStackedHeaderCellRenderer(_DataGridStateDetails dataGridStateDetails) { + _dataGridStateDetails = dataGridStateDetails; + } + + @override + void onInitializeDisplayWidget(DataCellBase dataCell, Widget widget) { + if (dataCell != null) { + final dataGridSettings = _dataGridStateDetails(); + final isLight = + dataGridSettings.dataGridThemeData.brightness == Brightness.light; + var label = DefaultTextStyle( + style: isLight + ? TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Colors.black87) + : TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1)), + child: dataCell._stackedHeaderCell.child); + + dataCell._columnElement = GridCell( + key: dataCell._key, + dataCell: dataCell, + padding: EdgeInsets.zero, + backgroundColor: isLight + ? Color.fromRGBO(255, 255, 255, 1) + : Color.fromRGBO(33, 33, 33, 1), + isDirty: dataGridSettings.container._isDirty || dataCell._isDirty, + child: ExcludeSemantics(child: label), + ); + + label = null; + } + } +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_widget_renderer.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_widget_renderer.dart index b49b4c8d1..7592896e2 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_widget_renderer.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_widget_renderer.dart @@ -15,6 +15,7 @@ class GridCellWidgetRenderer dataCell._columnElement = GridWidgetCell( key: dataCell._key, dataCell: dataCell, + padding: dataCell.gridColumn.padding ?? EdgeInsets.zero, backgroundColor: dataCell._cellStyle?.backgroundColor, isDirty: _dataGridStateDetails().container._isDirty || dataCell._isDirty, diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_header_cell_renderer.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_header_cell_renderer.dart index 2bb53c116..cfc560536 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_header_cell_renderer.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_header_cell_renderer.dart @@ -19,12 +19,11 @@ class GridHeaderCellRenderer style: dataCell._cellStyle?.textStyle ?? _dataGridStateDetails().dataGridThemeData.headerStyle.textStyle, ); - dataCell._columnElement = GridHeaderCell( key: dataCell._key, dataCell: dataCell, alignment: dataCell.gridColumn.headerTextAlignment, - padding: dataCell.gridColumn.padding, + padding: dataCell.gridColumn.headerPadding, backgroundColor: dataCell._cellStyle?.backgroundColor ?? _dataGridStateDetails() .dataGridThemeData @@ -41,6 +40,21 @@ class GridHeaderCellRenderer @override void setCellStyle(DataCellBase dataCell) { + TextStyle getDefaultHeaderTextStyle() { + return _dataGridStateDetails().dataGridThemeData.brightness == + Brightness.light + ? TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Colors.black87) + : TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1)); + } + if (dataCell != null) { dataCell ..cellValue = @@ -51,11 +65,18 @@ class GridHeaderCellRenderer // Uncomment the below code if the mentioned report has resolved from framework side // https://github.com/flutter/flutter/issues/29702 //this._dataGridStateDetails().dataGridThemeData?.headerStyle?.backgroundColor, - textStyle: dataCell.gridColumn.headerStyle?.textStyle ?? - _dataGridStateDetails() - .dataGridThemeData - ?.headerStyle - ?.textStyle); + textStyle: dataCell._ishover + ? (dataCell.gridColumn.headerStyle?.hoverTextStyle ?? + _dataGridStateDetails() + .dataGridThemeData + .headerStyle + .hoverTextStyle) + : (dataCell.gridColumn.headerStyle?.textStyle ?? + _dataGridStateDetails() + .dataGridThemeData + ?.headerStyle + ?.textStyle ?? + getDefaultHeaderTextStyle())); } } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_virtualizing_cell_renderer_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_virtualizing_cell_renderer_base.dart index aa13f1ad6..07ba34065 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_virtualizing_cell_renderer_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_virtualizing_cell_renderer_base.dart @@ -73,10 +73,32 @@ abstract class GridVirtualizingCellRendererBase { onTapDown: _handleOnTapDown, onDoubleTap: dataGridSettings.onCellDoubleTap != null ? () { - _handleOnDoubleTap(dataCell: dataCell); + _handleOnDoubleTap( + dataCell: dataCell, dataGridSettings: dataGridSettings); } : null, onLongPressEnd: (details) { @@ -283,37 +284,213 @@ class _RenderGridCell extends RenderBox final DataRow dataRow = dataCell._dataRow; final _DataGridSettings _dataGridSettings = dataRow._dataGridStateDetails(); - final _VisualContainerHelper container = _dataGridSettings.container; - final _VisibleLineInfo lineInfo = - dataRow._getColumnVisibleLineInfo(dataCell.columnIndex); - final double lineSize = - dataCell._dataRow._getColumnSize(dataCell.columnIndex, false); - - var origin = lineInfo != null ? lineInfo.origin : 0.0; - origin += container.horizontalOffset; - - //To overcome grid common RightToLeft clipping line creation problem - // instead of handling in grid common source. - if (_dataGridSettings.textDirection == TextDirection.rtl && - lineInfo != null && - lineInfo.visibleIndex == - _SfDataGridHelper.getVisibleLines(dataRow._dataGridStateDetails()) - .firstBodyVisibleIndex) { - origin += lineInfo.scrollOffset; + final double lineWidth = dataCell._dataRow._getColumnWidth( + dataCell.columnIndex, dataCell.columnIndex + dataCell._columnSpan); + final double lineHeight = dataCell._dataRow._getRowHeight( + dataCell.rowIndex - dataCell._rowSpan, dataCell.rowIndex); + + if (dataRow.rowType == RowType.stackedHeaderRow) { + columnRect = + _getStackedHeaderCellRect(_dataGridSettings, lineWidth, lineHeight); + } else { + final _VisibleLineInfo lineInfo = + dataRow._getColumnVisibleLineInfo(dataCell.columnIndex); + final double origin = lineInfo != null ? lineInfo.origin : 0.0; + columnRect = _getCellRect( + _dataGridSettings, lineInfo, origin, lineWidth, lineHeight); } + } else { + columnRect = Rect.zero; + } + + return columnRect; + } + + Rect _getCellRect( + _DataGridSettings dataGridSettings, + _VisibleLineInfo lineInfo, + double origin, + double lineWidth, + double lineHeight) { + final DataRow dataRow = dataCell._dataRow; + final int rowIndex = dataCell.rowIndex; + final int rowSpan = dataCell._rowSpan; + + origin += dataGridSettings.container.horizontalOffset; + + // To overcome grid common RightToLeft clipping line creation problem + // instead of handling in grid common source. + if (dataGridSettings.textDirection == TextDirection.rtl && + lineInfo != null && + lineInfo.visibleIndex == + _SfDataGridHelper.getVisibleLines(dataRow._dataGridStateDetails()) + .firstBodyVisibleIndex) { + origin += lineInfo.scrollOffset; + } + if (dataCell._cellType != CellType.stackedHeaderCell) { // Clipping the column when frozen column applied - cellClipRect = _getCellClipRect(_dataGridSettings, lineInfo, rowHeight); + cellClipRect = _getCellClipRect(dataGridSettings, lineInfo, lineHeight); + } + + final topPosition = (rowSpan > 0) + ? -dataRow._getRowHeight(rowIndex - rowSpan, rowIndex - 1) + : 0.0; + + columnRect = Rect.fromLTWH(origin, topPosition, lineWidth, lineHeight); + return columnRect; + } - columnRect = Rect.fromLTWH(origin, 0, lineSize, rowHeight); - origin = null; + Rect _getStackedHeaderCellRect( + _DataGridSettings dataGridSettings, double lineWidth, double lineHeight) { + final DataRow dataRow = dataCell._dataRow; + final int cellStartIndex = dataCell.columnIndex; + final int columnSpan = dataCell._columnSpan; + final int cellEndIndex = cellStartIndex + columnSpan; + final int frozenColumns = dataGridSettings.container.frozenColumns; + final int frozenColumnsCount = dataGridSettings.frozenColumnsCount; + final int footerFrozenColumns = + dataGridSettings.container.footerFrozenColumns; + final int footerFrozenColumnsCount = + dataGridSettings.footerFrozenColumnsCount; + final int columnsLength = dataGridSettings.columns.length; + final scrollColumns = dataGridSettings.container.scrollColumns; + Rect columnRect = Rect.zero; + double origin; + _VisibleLineInfo lineInfo; + + if (frozenColumns > cellStartIndex && frozenColumns <= cellEndIndex) { + if (dataGridSettings.textDirection == TextDirection.ltr) { + for (int index = cellEndIndex; + index >= frozenColumnsCount - 1; + index--) { + lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); + if (lineInfo != null) { + final startLineInfo = + scrollColumns.getVisibleLineAtLineIndex(cellStartIndex); + origin = startLineInfo.origin; + lineWidth = _getClippedWidth( + dataGridSettings, cellStartIndex, cellEndIndex); + break; + } + } + } else { + for (int index = cellEndIndex; index >= cellStartIndex; index--) { + lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); + if (lineInfo != null) { + origin = lineInfo.origin < 0 ? 0.0 : lineInfo.origin; + lineWidth = _getClippedWidth( + dataGridSettings, cellStartIndex, cellEndIndex); + if (lineInfo.origin < 0) { + lineWidth += lineInfo.origin; + } + break; + } + } + } + } else if (footerFrozenColumns > 0 && + columnsLength - footerFrozenColumnsCount <= cellEndIndex) { + int span = 0; + if (dataGridSettings.textDirection == TextDirection.ltr) { + for (int index = cellStartIndex; index <= cellEndIndex; index++) { + lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); + if (lineInfo != null) { + if (index == columnsLength - footerFrozenColumns) { + origin = lineInfo.origin; + lineWidth = + dataRow._getColumnWidth(cellStartIndex + span, cellEndIndex); + break; + } else { + origin = lineInfo.clippedOrigin; + lineWidth = _getClippedWidth( + dataGridSettings, cellStartIndex, cellEndIndex); + break; + } + } + span += 1; + } + } else { + var span = 0; + for (int index = cellStartIndex; index <= cellEndIndex; index++) { + lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); + if (lineInfo != null) { + final line = scrollColumns.getVisibleLineAtLineIndex(cellEndIndex); + if (index == columnsLength - footerFrozenColumnsCount) { + origin = line.origin; + lineWidth = + dataRow._getColumnWidth(cellStartIndex + span, cellEndIndex); + break; + } else { + origin = line.clippedOrigin - lineInfo.scrollOffset; + lineWidth = _getClippedWidth( + dataGridSettings, cellStartIndex, cellEndIndex); + break; + } + } + span += 1; + } + } } else { - columnRect = Rect.zero; + var span = dataCell._columnSpan; + if (dataGridSettings.textDirection == TextDirection.ltr) { + for (int index = cellStartIndex; index <= cellEndIndex; index++) { + lineInfo = dataRow._getColumnVisibleLineInfo(index); + if (lineInfo != null) { + origin = lineInfo.origin + + dataRow._getColumnWidth(index, index + span) - + dataRow._getColumnWidth(cellStartIndex, cellEndIndex); + + cellClipRect = _getSpannedCellClipRect( + dataGridSettings, dataRow, dataCell, lineHeight, lineWidth); + break; + } + span -= 1; + } + } else { + for (int index = cellEndIndex; index >= cellStartIndex; index--) { + lineInfo = dataRow._getColumnVisibleLineInfo(index); + if (lineInfo != null) { + origin = lineInfo.origin + + dataRow._getColumnWidth(index - span, index) - + dataRow._getColumnWidth(cellStartIndex, cellEndIndex); + + cellClipRect = _getSpannedCellClipRect( + dataGridSettings, dataRow, dataCell, lineHeight, lineWidth); + break; + } + span -= 1; + } + } } + if (lineInfo != null) { + columnRect = _getCellRect( + dataGridSettings, lineInfo, origin, lineWidth, lineHeight); + } return columnRect; } + double _getClippedWidth( + _DataGridSettings dataGridSettings, int startIndex, int endIndex) { + double clippedWidth = 0; + for (int index = startIndex; index <= endIndex; index++) { + final newline = dataCell._dataRow._getColumnVisibleLineInfo(index); + if (newline != null) { + if (dataGridSettings.textDirection == TextDirection.ltr) { + clippedWidth += + newline.isClipped ? newline.clippedSize : newline.size; + } else { + clippedWidth += newline.isClipped + ? newline.clippedCornerExtent > 0 + ? newline.clippedCornerExtent + : newline.clippedSize + : newline.size; + } + } + } + return clippedWidth; + } + @override bool hitTestChildren(BoxHitTestResult result, {Offset position}) { if (child == null) { @@ -368,38 +545,62 @@ BorderDirectional _getCellBorder(DataCellBase dataCell) { final Color borderColor = dataGridSettings.dataGridThemeData?.gridLineColor; final borderWidth = dataGridSettings.dataGridThemeData?.gridLineStrokeWidth; - final bool canDrawHorizontalBorder = - dataGridSettings.gridLinesVisibility == GridLinesVisibility.horizontal || - dataGridSettings.gridLinesVisibility == GridLinesVisibility.both; - - final bool canDrawVerticalBorder = - dataGridSettings.gridLinesVisibility == GridLinesVisibility.vertical || - dataGridSettings.gridLinesVisibility == GridLinesVisibility.both; + final rowIndex = (dataCell._rowSpan > 0) + ? dataCell.rowIndex - dataCell._rowSpan + : dataCell.rowIndex; + final columnIndex = dataCell.columnIndex; + final isStackedHeaderCell = dataCell._cellType == CellType.stackedHeaderCell; + final isHeaderCell = dataCell._cellType == CellType.headerCell; + + final bool canDrawHeaderHorizontalBorder = + ((dataGridSettings.headerGridLinesVisibility == + GridLinesVisibility.horizontal || + dataGridSettings.headerGridLinesVisibility == + GridLinesVisibility.both) && + (isHeaderCell || isStackedHeaderCell)); + + final bool canDrawHeaderVerticalBorder = + ((dataGridSettings.headerGridLinesVisibility == + GridLinesVisibility.vertical || + dataGridSettings.headerGridLinesVisibility == + GridLinesVisibility.both) && + (isHeaderCell || isStackedHeaderCell)); + + final bool canDrawHorizontalBorder = ((dataGridSettings.gridLinesVisibility == + GridLinesVisibility.horizontal || + dataGridSettings.gridLinesVisibility == GridLinesVisibility.both) && + !isHeaderCell && + !isStackedHeaderCell); + + final bool canDrawVerticalBorder = ((dataGridSettings.gridLinesVisibility == + GridLinesVisibility.vertical || + dataGridSettings.gridLinesVisibility == GridLinesVisibility.both) && + !isStackedHeaderCell && + !isHeaderCell); // Frozen column and row checking - final bool canDrawBottomFrozenBorder = - dataGridSettings.frozenRowsCount.isFinite && - dataGridSettings.frozenRowsCount > 0 && - _GridIndexResolver.getLastFrozenRowIndex(dataGridSettings) == - dataCell.rowIndex; + final bool canDrawBottomFrozenBorder = dataGridSettings + .frozenRowsCount.isFinite && + dataGridSettings.frozenRowsCount > 0 && + _GridIndexResolver.getLastFrozenRowIndex(dataGridSettings) == rowIndex; final bool canDrawTopFrozenBorder = dataGridSettings.footerFrozenRowsCount.isFinite && dataGridSettings.footerFrozenRowsCount > 0 && _GridIndexResolver.getStartFooterFrozenRowIndex(dataGridSettings) == - dataCell.rowIndex; + rowIndex; final bool canDrawRightFrozenBorder = dataGridSettings.frozenColumnsCount.isFinite && dataGridSettings.frozenColumnsCount > 0 && _GridIndexResolver.getLastFrozenColumnIndex(dataGridSettings) == - dataCell.columnIndex; + columnIndex; final bool canDrawLeftFrozenBorder = dataGridSettings .footerFrozenColumnsCount.isFinite && dataGridSettings.footerFrozenColumnsCount > 0 && _GridIndexResolver.getStartFooterFrozenColumnIndex(dataGridSettings) == - dataCell.columnIndex; + columnIndex; final Color frozenPaneLineColor = dataGridSettings.dataGridThemeData?.frozenPaneLineColor; @@ -408,13 +609,16 @@ BorderDirectional _getCellBorder(DataCellBase dataCell) { dataGridSettings.dataGridThemeData?.frozenPaneLineWidth; BorderSide _getLeftBorder() { - if ((dataCell.columnIndex == 0 && canDrawVerticalBorder) || + if ((columnIndex == 0 && + (canDrawVerticalBorder || canDrawHeaderVerticalBorder)) || canDrawLeftFrozenBorder) { - if (canDrawLeftFrozenBorder) { + if (canDrawLeftFrozenBorder && !isStackedHeaderCell) { return BorderSide( width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else { + } else if (canDrawVerticalBorder || canDrawHeaderVerticalBorder) { return BorderSide(width: borderWidth, color: borderColor); + } else { + return BorderSide.none; } } else { return BorderSide.none; @@ -422,9 +626,10 @@ BorderDirectional _getCellBorder(DataCellBase dataCell) { } BorderSide _getTopBorder() { - if ((dataCell.rowIndex == 0 && canDrawHorizontalBorder) || + if ((rowIndex == 0 && + (canDrawHorizontalBorder || canDrawHeaderHorizontalBorder)) || canDrawTopFrozenBorder) { - if (canDrawTopFrozenBorder) { + if (canDrawTopFrozenBorder && !isStackedHeaderCell) { return BorderSide( width: frozenPaneLineWidth, color: frozenPaneLineColor); } else { @@ -436,12 +641,16 @@ BorderDirectional _getCellBorder(DataCellBase dataCell) { } BorderSide _getRightBorder() { - if (canDrawVerticalBorder || canDrawRightFrozenBorder) { - if (canDrawRightFrozenBorder) { + if (canDrawVerticalBorder || + canDrawHeaderVerticalBorder || + canDrawRightFrozenBorder) { + if (canDrawRightFrozenBorder && !isStackedHeaderCell) { return BorderSide( width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else { + } else if (canDrawVerticalBorder || canDrawHeaderVerticalBorder) { return BorderSide(width: borderWidth, color: borderColor); + } else { + return BorderSide.none; } } else { return BorderSide.none; @@ -449,8 +658,10 @@ BorderDirectional _getCellBorder(DataCellBase dataCell) { } BorderSide _getBottomBorder() { - if (canDrawHorizontalBorder || canDrawBottomFrozenBorder) { - if (canDrawBottomFrozenBorder) { + if (canDrawHorizontalBorder || + canDrawHeaderHorizontalBorder || + canDrawBottomFrozenBorder) { + if (canDrawBottomFrozenBorder && !isStackedHeaderCell) { return BorderSide( width: frozenPaneLineWidth, color: frozenPaneLineColor); } else { @@ -503,11 +714,33 @@ Widget _wrapInsideCellContainer( ); } + double getCellHeight(DataCell dataCell, double defaultHeight) { + double height; + if (dataCell._rowSpan > 0) { + height = dataCell._dataRow._getRowHeight( + dataCell.rowIndex - dataCell._rowSpan, dataCell.rowIndex); + } else { + height = defaultHeight; + } + return height; + } + + double getCellWidth(DataCell dataCell, double defaultWidth) { + double width; + if (dataCell._columnSpan > 0) { + width = dataCell._dataRow._getColumnWidth( + dataCell.columnIndex, dataCell.columnIndex + dataCell._columnSpan); + } else { + width = defaultWidth; + } + return width; + } + return LayoutBuilder( builder: (context, constraint) => Container( key: key, - width: constraint.maxWidth, - height: constraint.maxHeight, + width: getCellWidth(dataCell, constraint.maxWidth), + height: getCellHeight(dataCell, constraint.maxHeight), padding: _getPadding(dataCell, padding), alignment: alignment, clipBehavior: Clip.antiAlias, @@ -525,11 +758,16 @@ EdgeInsets _getPadding(DataCellBase dataCell, EdgeInsets padding) { final themeData = dataGridSettings.dataGridThemeData; final currentCellBorderWidth = themeData.currentCellStyle.borderWidth ?? 1.0; + final visualDensityPadding = dataGridSettings.visualDensity.vertical * 2; + + padding ??= EdgeInsets.all(16) + + EdgeInsets.fromLTRB(0.0, visualDensityPadding, 0.0, visualDensityPadding); padding = padding != null ? padding - EdgeInsets.all(dataCell.isCurrentCell ? currentCellBorderWidth : 0.0) : padding; + return padding != null && padding.isNonNegative ? padding : EdgeInsets.zero; } @@ -574,6 +812,134 @@ Rect _getCellClipRect(_DataGridSettings _dataGridSettings, } } +Rect _getSpannedCellClipRect( + _DataGridSettings dataGridSettings, + DataRow dataRow, + DataCellBase dataCell, + double cellHeight, + double cellWidth) { + Rect clipRect; + var firstVisibleStackedColumnIndex = dataCell.columnIndex; + double lastCellClippedSize = 0.0; + var isLastCellClippedCorner = false; + var isLastCellClippedBody = false; + + double getClippedWidth(DataCellBase dataCell, _SpannedDataRow dataRow, + {bool columnsNotInViewWidth = false, bool allCellsClippedWidth = false}) { + final startIndex = dataCell.columnIndex; + final endIndex = dataCell.columnIndex + dataCell._columnSpan; + double clippedWidth = 0; + for (int index = startIndex; index <= endIndex; index++) { + final newline = dataRow._getColumnVisibleLineInfo(index); + if (columnsNotInViewWidth) { + if (newline == null) { + clippedWidth += + dataGridSettings.container.scrollColumns.getLineSize(index); + } else { + firstVisibleStackedColumnIndex = index; + break; + } + } + if (allCellsClippedWidth) { + if (newline != null) { + if (dataGridSettings.textDirection == TextDirection.ltr) { + clippedWidth += + newline.isClipped ? newline.clippedSize : newline.size; + } else { + clippedWidth += newline.isClipped + ? newline.clippedCornerExtent > 0 + ? newline.clippedCornerExtent + : newline.clippedSize + : newline.size; + } + lastCellClippedSize = newline.clippedSize; + isLastCellClippedCorner = newline.isClippedCorner; + isLastCellClippedBody = newline.isClippedBody; + } + } + } + return clippedWidth; + } + + if (dataGridSettings.frozenColumnsCount < 0 || + dataGridSettings.footerFrozenColumnsCount < 0) { + return null; + } + + if (dataRow != null && dataCell._renderer != null) { + final columnsNotInViewWidth = + getClippedWidth(dataCell, dataRow, columnsNotInViewWidth: true); + final clippedWidth = + getClippedWidth(dataCell, dataRow, allCellsClippedWidth: true); + final visiblelineInfo = + dataRow._getColumnVisibleLineInfo(firstVisibleStackedColumnIndex); + + if (visiblelineInfo != null) { + if (visiblelineInfo.isClippedOrigin && visiblelineInfo.isClippedCorner) { + final clippedOrigin = columnsNotInViewWidth + + visiblelineInfo.size - + (visiblelineInfo.clippedSize + visiblelineInfo.clippedCornerExtent); + + final double left = dataGridSettings.textDirection == TextDirection.ltr + ? clippedOrigin + : visiblelineInfo.clippedSize; + final double right = dataGridSettings.textDirection == TextDirection.ltr + ? clippedWidth + : visiblelineInfo.clippedCornerExtent; + + clipRect = Rect.fromLTWH(left, 0.0, right, cellHeight); + } else if (visiblelineInfo.isClippedOrigin) { + final clippedOriginLTR = columnsNotInViewWidth + + visiblelineInfo.size - + visiblelineInfo.clippedSize; + final clippedOriginRTL = + (isLastCellClippedCorner && isLastCellClippedBody) + ? lastCellClippedSize + : 0.0; + + final double left = dataGridSettings.textDirection == TextDirection.ltr + ? clippedOriginLTR + : clippedOriginRTL; + final double right = dataGridSettings.textDirection == TextDirection.ltr + ? clippedWidth + : cellWidth - + (columnsNotInViewWidth + visiblelineInfo.scrollOffset); + + clipRect = Rect.fromLTWH(left, 0.0, right, cellHeight); + } else if (isLastCellClippedCorner && isLastCellClippedBody) { + final double left = dataGridSettings.textDirection == TextDirection.ltr + ? columnsNotInViewWidth + : dataCell.columnIndex < firstVisibleStackedColumnIndex + ? 0.0 + : cellWidth - clippedWidth; + final double right = dataGridSettings.textDirection == TextDirection.ltr + ? clippedWidth + : cellWidth; + + clipRect = Rect.fromLTWH(left, 0.0, right, cellHeight); + } else { + if (clippedWidth < cellWidth) { + double left; + if (dataCell.columnIndex < firstVisibleStackedColumnIndex) { + left = dataGridSettings.textDirection == TextDirection.ltr + ? cellWidth - clippedWidth + : 0.0; + } else { + left = dataGridSettings.textDirection == TextDirection.ltr + ? 0.0 + : cellWidth - clippedWidth; + } + + clipRect = Rect.fromLTWH(left, 0.0, clippedWidth, cellHeight); + } else if (clipRect != null) { + clipRect = null; + } + } + } + } + return clipRect; +} + // Gesture Events void _handleOnTapUp( diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_header_cell_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_header_cell_widget.dart index 33de6621f..606f5baf3 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_header_cell_widget.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_header_cell_widget.dart @@ -182,6 +182,24 @@ class _GridHeaderCellState extends State { child = checkHeaderCellConstraints(child); final alignment = !isSortIconLoaded ? widget.alignment : null; + EdgeInsets _getPadding(DataCellBase dataCell, EdgeInsets padding) { + final dataGridSettings = dataCell._dataRow?._dataGridStateDetails(); + + if (dataGridSettings == null) { + return const EdgeInsets.all(0.0); + } + + final visualDensityPadding = dataGridSettings.visualDensity.vertical * 2; + + padding ??= EdgeInsets.all(16) + + EdgeInsets.fromLTRB( + 0.0, visualDensityPadding, 0.0, visualDensityPadding); + + return padding != null && padding.isNonNegative + ? padding + : EdgeInsets.zero; + } + return Container( key: widget.key, clipBehavior: Clip.antiAlias, @@ -193,17 +211,78 @@ class _GridHeaderCellState extends State { key: widget.key, backgroundColor: widget.backgroundColor, alignment: alignment, - padding: widget.padding, + padding: headerCellWidget == null + ? _getPadding(widget.dataCell, widget.padding) + : null, )); } + Color _getHoverBackgroundColor() { + final _DataGridSettings dataGridSettings = + widget.dataCell?._dataRow?._dataGridStateDetails(); + if (dataGridSettings == null) { + return null; + } + + final hoverColor = (widget.dataCell.gridColumn?.headerStyle?.hoverColor ?? + dataGridSettings.dataGridThemeData.headerStyle?.hoverColor) ?? + ((dataGridSettings.dataGridThemeData.brightness == Brightness.light) + ? Color.fromRGBO(245, 245, 245, 1) + : Color.fromRGBO(66, 66, 66, 1)); + return hoverColor; + } + + void onMouseHover() { + final _DataGridSettings dataGridSettings = + widget.dataCell?._dataRow?._dataGridStateDetails(); + + if (dataGridSettings != null && widget.dataCell != null) { + widget.dataCell + .._ishover = true + .._isDirty = true + .._updateColumn(); + dataGridSettings.source + .notifyDataSourceListeners(propertyName: 'hoverOnHeaderCell'); + } + } + + void onMouseExit() { + final _DataGridSettings dataGridSettings = + widget.dataCell?._dataRow?._dataGridStateDetails(); + + if (dataGridSettings != null && widget.dataCell != null) { + widget.dataCell + .._ishover = false + .._isDirty = true + .._updateColumn(); + dataGridSettings.source + .notifyDataSourceListeners(propertyName: 'hoverOnHeaderCell'); + } + } + @override Widget build(BuildContext context) { final Widget child = Semantics( label: widget.dataCell.cellValue.toString(), - child: _wrapInsideGestureDetector(), + child: MouseRegion( + onHover: (event) { + onMouseHover(); + }, + onExit: (event) { + onMouseExit(); + }, + child: Material( + color: Colors.transparent, + child: InkWell( + mouseCursor: SystemMouseCursors.basic, + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + hoverColor: _getHoverBackgroundColor(), + onTap: () {}, + child: _wrapInsideGestureDetector(), + )), + ), ); - return _GridCellRenderObjectWidget( key: widget.key, dataCell: widget.dataCell, @@ -303,7 +382,7 @@ class _GridHeaderCellState extends State { column.allowSorting && dataGridSettings.allowSorting) { final sortColumnName = column.mappingName; - final allowMultiSort = kIsWeb + final allowMultiSort = dataGridSettings._isDesktop ? (dataGridSettings.isControlKeyPressed && dataGridSettings.allowMultiColumnSorting) : dataGridSettings.allowMultiColumnSorting; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/virtualizing_cells_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/virtualizing_cells_widget.dart index 5a02e3b85..7d5f1167a 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/virtualizing_cells_widget.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/virtualizing_cells_widget.dart @@ -128,7 +128,11 @@ class _RenderVirtualizingCellsWidget extends RenderBox if (dataRow.rowIndex > _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - origin -= dataGridSettings.headerRowHeight; + final double headerRowsHeight = container.scrollRows + .rangeToRegionPoints( + 0, dataGridSettings.headerLineCount - 1, true)[1] + .length; + origin -= headerRowsHeight; } // Clipping the column when frozen row applied @@ -174,6 +178,60 @@ class _RenderVirtualizingCellsWidget extends RenderBox final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails(); final rect = _getRowRect(dataGridSettings, offset); var backgroundColor = Colors.transparent; + + Color getDefaultSelectionBackgroundColor() { + return dataGridSettings.dataGridThemeData.brightness == Brightness.light + ? Color.fromRGBO(238, 238, 238, 1) + : Color.fromRGBO(48, 48, 48, 1); + } + + Color getDefaultHeaderBackgroundColor() { + return dataGridSettings.dataGridThemeData.brightness == Brightness.light + ? Color.fromRGBO(255, 255, 255, 1) + : Color.fromRGBO(33, 33, 33, 1); + } + + void drawSpannedRowBackgroundColor(Color backgroundColor) { + final bool isRowSpanned = + dataRow._visibleColumns.any((dataCell) => dataCell._rowSpan > 0); + + if (isRowSpanned) { + _RenderGridCell child = lastChild; + while (child != null) { + final _VirtualizingCellWidgetParentData childParentData = + child.parentData; + final dataCell = child.dataCell; + final lineInfo = + dataRow._getColumnVisibleLineInfo(dataCell.columnIndex); + if (dataCell._rowSpan > 0 && lineInfo != null) { + final columnRect = child.columnRect; + final cellClipRect = child.cellClipRect; + final height = dataRow._getRowHeight( + dataCell.rowIndex - dataCell._rowSpan, dataCell.rowIndex); + Rect cellRect = Rect.zero; + if (child.cellClipRect != null) { + double left = columnRect.left; + double width = cellClipRect.width; + if (cellClipRect.left > 0 && columnRect.width <= width) { + left += cellClipRect.left; + width = columnRect.width - cellClipRect.left; + } else if (cellClipRect.left > 0 && width < columnRect.width) { + left += cellClipRect.left; + } + + cellRect = Rect.fromLTWH(left, columnRect.top, width, height); + } else { + cellRect = Rect.fromLTWH( + columnRect.left, columnRect.top, columnRect.width, height); + } + dataGridSettings.gridPaint?.color = backgroundColor; + context.canvas.drawRect(cellRect, dataGridSettings.gridPaint); + } + child = childParentData.previousSibling; + } + } + } + if (dataGridSettings != null && dataGridSettings.gridPaint != null && dataRow != null) { @@ -182,7 +240,13 @@ class _RenderVirtualizingCellsWidget extends RenderBox if (dataRow.rowRegion == RowRegion.header && dataRow.rowType == RowType.headerRow) { backgroundColor = - dataGridSettings.dataGridThemeData.headerStyle.backgroundColor; + dataGridSettings.dataGridThemeData.headerStyle.backgroundColor ?? + getDefaultHeaderBackgroundColor(); + drawSpannedRowBackgroundColor(backgroundColor); + } else if (dataRow.rowRegion == RowRegion.header && + dataRow.rowType == RowType.stackedHeaderRow) { + backgroundColor = getDefaultHeaderBackgroundColor(); + drawSpannedRowBackgroundColor(backgroundColor); } else { backgroundColor = dataRow.isSelectedRow ? dataRow.rowStyle != null && @@ -194,23 +258,25 @@ class _RenderVirtualizingCellsWidget extends RenderBox 0.5) .backgroundColor : dataGridSettings - .dataGridThemeData.selectionStyle.backgroundColor + .dataGridThemeData.selectionStyle.backgroundColor ?? + getDefaultSelectionBackgroundColor() : dataRow.rowStyle != null ? dataRow.rowStyle.backgroundColor ?? dataGridSettings.dataGridThemeData.cellStyle.backgroundColor : dataGridSettings.dataGridThemeData.cellStyle.backgroundColor; } + // Default theme color are common for both the HeaderBackgroundColor and + // CellBackgroundColor, so we have checked commonly at outside of the + // condition + backgroundColor ??= getDefaultHeaderBackgroundColor(); + dataGridSettings.gridPaint?.color = backgroundColor; context.canvas.drawRect(rect, dataGridSettings.gridPaint); } } void _drawCurrentRowBorder(PaintingContext context, Offset offset) { - if (!kIsWeb) { - return; - } - final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails(); if (dataGridSettings.boxPainter != null && @@ -286,6 +352,26 @@ class _RenderVirtualizingCellsWidget extends RenderBox return false; } + @override + bool hitTest(BoxHitTestResult result, {Offset position}) { + final bool isRowSpanned = + dataRow._visibleColumns.any((dataCell) => dataCell._rowSpan > 0); + + if (isRowSpanned) { + _RenderGridCell child = lastChild; + while (child != null) { + final _VirtualizingCellWidgetParentData childParentData = + child.parentData; + if (child.columnRect != null && child.columnRect.contains(position)) { + return super.hitTest(result, + position: Offset(position.dx.abs(), position.dy.abs())); + } + child = childParentData.previousSibling; + } + } + return super.hitTest(result, position: position); + } + @override void performLayout() { void _layout({RenderBox child, double width, double height}) { @@ -298,18 +384,15 @@ class _RenderVirtualizingCellsWidget extends RenderBox final _VirtualizingCellWidgetParentData _parentData = child.parentData; final _RenderGridCell gridCell = child; if (dataRow.isVisible && gridCell.dataCell.isVisible) { - size = constraints - .constrain(Size(constraints.maxWidth, constraints.maxHeight)); final Rect columnRect = gridCell._measureColumnRect(constraints.maxHeight); + size = constraints.constrain(Size(columnRect.width, columnRect.height)); _parentData ..width = columnRect.width ..height = columnRect.height ..cellClipRect = gridCell.cellClipRect; _layout( - child: child, - width: _parentData.width, - height: constraints.maxHeight); + child: child, width: _parentData.width, height: _parentData.height); _parentData.offset = Offset(columnRect.left, columnRect.top); } else { size = constraints.constrain(Size.zero); @@ -348,8 +431,10 @@ class _RenderVirtualizingCellsWidget extends RenderBox } child = childParentData.nextSibling; } - - _drawCurrentRowBorder(context, offset); + final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails(); + if (dataGridSettings._isDesktop) { + _drawCurrentRowBorder(context, offset); + } } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/datagrid_datasource.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/datagrid_datasource.dart index 635fa0d3f..9f82de53a 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/datagrid_datasource.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/datagrid_datasource.dart @@ -11,8 +11,8 @@ typedef _DataGridSourceListener = void Function( /// * [dataSource] - The number of rows in a datagrid and row selection depends /// on the [dataSource]. So, set the collection required for datagrid in /// [dataSource]. -/// * [getCellValue] - The data needed for the cells is obtained from -/// [getCellValue]. +/// * [getValue] - The data needed for the cells is obtained from +/// [getValue]. /// /// Call the [notifyDataSourceListeners] when performing CRUD in the underlying /// datasource. When updating data in a cell, pass the corresponding @@ -23,25 +23,24 @@ typedef _DataGridSourceListener = void Function( /// ``` dart /// final List _employees = []; /// -/// class EmployeeDataSource extends DataGridSource -/// { +/// class EmployeeDataSource extends DataGridSource { /// @override -/// List get dataSource => _employees; +/// List get dataSource => _employees; /// /// @override -/// getCellValue(int rowIndex, String columnName){ +/// getValue(Employee employee, String columnName){ /// switch (columnName) { /// case 'id': -/// return employees[rowIndex].id; +/// return employee.id; /// break; /// case 'name': -/// return employees[rowIndex].name; +/// return employee.name; /// break; /// case 'salary': -/// return employees[rowIndex].salary; +/// return employee.salary; /// break; /// case 'designation': -/// return employees[rowIndex].designation; +/// return employee.designation; /// break; /// default: /// return ' '; @@ -77,6 +76,54 @@ abstract class DataGridSource /// Called to obtain the data for the cells from the corresponding object. Object getValue(T data, String columnName) => null; + /// 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. + /// + /// Returning true may impact performance as the column widths are + /// recalculated again (whenever the notifyListeners is called). + /// + /// If you are aware that column widths are going to be same whenever + /// underlying data changes, return 'false' from this method. + /// + /// Note: Column widths will be recalculated automatically whenever a new + /// instance of DataGridSource is assigned to SfDataGrid. + /// ``` dart + /// class EmployeeDataSource extends DataGridSource { + /// @override + /// List get dataSource => _employees; + /// + /// @override + /// bool shouldRecalculateColumnWidths() { + /// return true; + /// } + /// + /// @override + /// getValue(Employee employee, String columnName){ + /// switch (columnName) { + /// case 'id': + /// return employee.id; + /// break; + /// case 'name': + /// return employee.name; + /// break; + /// case 'salary': + /// return employee.salary; + /// break; + /// case 'designation': + /// return employee.designation; + /// break; + /// default: + /// return ' '; + /// break; + /// } + /// } + /// ``` + @protected + bool shouldRecalculateColumnWidths() { + return false; + } + @override bool operator ==(Object other) => identical(this, other) || @@ -159,33 +206,91 @@ abstract class DataGridSource return true; } _effectiveDataSource.sort((a, b) { - return _compare(sortedColumns, a, b); + return _compareValues(sortedColumns, a, b); }); return true; } - int _compare(List sortedColumns, Object a, Object b) { + int _compareValues( + List sortedColumns, Object a, Object b) { if (sortedColumns.length > 1) { for (int i = 0; i < sortedColumns.length; i++) { - final SortColumnDetails _sortColumn = sortedColumns[i]; - final Object value1 = getValue(a, _sortColumn.name); - final Object value2 = getValue(b, _sortColumn.name); - final compareResult = - _compareTo(value1, value2, _sortColumn.sortDirection); + final SortColumnDetails sortColumn = sortedColumns[i]; + final compareResult = compare(a, b, sortColumn); if (compareResult != 0) { return compareResult; } else { final List remainingSortColumns = sortedColumns - .skipWhile((value) => value == _sortColumn) + .skipWhile((value) => value == sortColumn) .toList(growable: false); - return _compare(remainingSortColumns, a, b); + return _compareValues(remainingSortColumns, a, b); } } } - final _sortColumn = sortedColumns.last; - final Object value1 = getValue(a, _sortColumn.name); - final Object value2 = getValue(b, _sortColumn.name); - return _compareTo(value1, value2, _sortColumn.sortDirection); + final sortColumn = sortedColumns.last; + return compare(a, b, sortColumn); + } + + /// Called when the sorting is applied for column. This method compares the + /// two objects and returns the order either they are equal, or one is + /// greater than or lesser than the other. + /// + /// You can return the following values, + /// * a negative integer if a is smaller than b, + /// * zero if a is equal to b, and + /// * a positive integer if a is greater than b. + /// + /// You can override this method and do the custom sorting based + /// on your requirement. Here [sortColumn] provides the details about the + /// column which is currently sorted with the sort direction. You can get the + /// currently sorted column and do the custom sorting for specific column. + /// + /// + /// The below example shows how to sort the `name` column based on the case + /// insensitive in ascending or descending order. + /// + /// ```dart + /// class EmployeeDataSource extends DataGridSource { + /// @override + /// List get dataSource => _employees + /// + /// @override + /// getValue(Employee employee, String columnName) { + /// switch (columnName) { + /// case 'id': + /// return employee.id; + /// break; + /// case 'name': + /// return employee.name; + /// break; + /// case 'salary': + /// return employee.salary; + /// break; + /// case 'designation': + /// return employee.designation; + /// break; + /// default: + /// return ' '; + /// break; + /// } + /// } + /// + /// @override + /// int compare(Employee a, Employee b, SortColumnDetails sortColumn) { + /// if (sortColumn.name == 'name') { + /// if (sortColumn.sortDirection == DataGridSortDirection.ascending) + /// return a.name.toLowerCase().compareTo(b.name.toLowerCase()); + /// else + /// return b.name.toLowerCase().compareTo(a.name.toLowerCase()); + /// } + /// return super.compare(a, b, sortColumn); + /// } + /// ``` + @protected + int compare(T a, T b, SortColumnDetails sortColumn) { + final Object value1 = getValue(a, sortColumn.name); + final Object value2 = getValue(b, sortColumn.name); + return _compareTo(value1, value2, sortColumn.sortDirection); } int _compareTo( @@ -264,6 +369,20 @@ abstract class DataGridSource /// You can use this indexer when you get the data for the widget that you /// return in [SfDataGrid.cellBuilder] callback for [GridWidgetColumn]. T operator [](int index) => _effectiveDataSource[index]; + + /// Called when [LoadMoreRows] function is called from the + /// [loadMoreViewBuilder]. + /// + /// Call the [notifyListeners] to refresh the datagrid based on current + /// available rows. + /// + /// See also, + /// + /// [SfDataGrid.loadMoreViewBuilder] - A builder that sets the widget to + /// display at end of the datagrid when end of the datagrid is reached on + /// vertical scrolling. + @protected + Future handleLoadMoreRows() async {} } /// Controls a [SfDataGrid] widget. @@ -347,7 +466,7 @@ class DataGridController extends DataGridSourceChangeNotifier { /// So, the row height can be reset based on the modified data. /// This is useful when setting auto row height /// using [SfDataGrid.onQueryRowHeight] callback. - void refreshRow(int rowIndex, {bool recalculateRowHeight}) { + void refreshRow(int rowIndex, {bool recalculateRowHeight = false}) { notifyDataSourceListeners( rowColumnIndex: RowColumnIndex(rowIndex, -1), propertyName: 'refreshRow', diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell_base.dart index 879995832..b5d6eda58 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell_base.dart @@ -7,6 +7,9 @@ abstract class DataCellBase { _isVisible = true; _isEnsured = false; _isDirty = false; + _columnSpan = 0; + _rowSpan = 0; + _ishover = false; } Widget _columnElement; @@ -27,6 +30,8 @@ abstract class DataCellBase { bool _isDirty; + bool _ishover; + String _displayText; /// The column index of the [DataCell]. @@ -44,6 +49,12 @@ abstract class DataCellBase { /// Decides whether the [DataCell] has the currentcell. bool isCurrentCell = false; + int _columnSpan; + + int _rowSpan; + + StackedHeaderCell _stackedHeaderCell; + /// Decides whether the [DataCell] is visible. bool get isVisible => _isVisible; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row.dart index 6c223285b..0ee42f7af 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row.dart @@ -149,8 +149,10 @@ class DataRow extends DataRowBase { } } - DataCellBase _createColumn(int index, {int columnHeightIncrementation = 0}) { + DataCellBase _createColumn(int index) { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + final canIncrementHeight = rowType == RowType.headerRow && + dataGridSettings.stackedHeaderRows.isNotEmpty; final dc = DataCell() .._dataRow = this ..columnIndex = index @@ -172,6 +174,12 @@ class DataRow extends DataRowBase { : dataGridSettings.cellRenderers['TextField'] .._cellType = CellType.gridCell; } + if (canIncrementHeight) { + final rowSpan = _StackedHeaderHelper._getRowSpan( + dataGridSettings, dc._dataRow.rowIndex - 1, index, false, + mappingName: dc.gridColumn.mappingName); + dc._rowSpan = rowSpan; + } dc._columnElement = dc._onInitializeColumnElement(false); return dc; @@ -199,6 +207,8 @@ class DataRow extends DataRowBase { void _updateColumn(DataCellBase dc, int index) { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + final canIncrementHeight = rowType == RowType.headerRow && + dataGridSettings.stackedHeaderRows.isNotEmpty; if (dc != null) { if (index < 0 || index >= dataGridSettings.container.columnCount) { dc._isVisible = false; @@ -213,6 +223,14 @@ class DataRow extends DataRowBase { dc.gridColumn = dataGridSettings.columns[columnIndex]; _checkForCurrentCell(dataGridSettings, dc); _updateRenderer(dataGridSettings, dc, dc.gridColumn); + if (canIncrementHeight) { + final rowSpan = _StackedHeaderHelper._getRowSpan( + dataGridSettings, dc._dataRow.rowIndex - 1, index, false, + mappingName: dc.gridColumn.mappingName); + dc._rowSpan = rowSpan; + } else { + dc._rowSpan = 0; + } dc .._columnElement = dc._onInitializeColumnElement(false) .._isEnsured = true; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row_base.dart index 2d4dacd58..601516ae5 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row_base.dart @@ -70,16 +70,38 @@ abstract class DataRowBase { .scrollColumns .getVisibleLineAtLineIndex(index); - double _getColumnSize(int index, bool lineNull) { - if (lineNull) { + _VisibleLineInfo _getRowVisibleLineInfo(int index) => _dataGridStateDetails() + .container + .scrollRows + .getVisibleLineAtLineIndex(index); + + double _getColumnWidth(int startIndex, int endIndex) { + if (startIndex != endIndex) { final currentPos = _dataGridStateDetails() .container .scrollColumns - .rangeToRegionPoints(index, index, true); + .rangeToRegionPoints(startIndex, endIndex, true); + return currentPos[1].length; + } + + final line = _getColumnVisibleLineInfo(startIndex); + if (line == null) { + return 0; + } + + return line.size; + } + + double _getRowHeight(int startIndex, int endIndex) { + if (startIndex != endIndex) { + final currentPos = _dataGridStateDetails() + .container + .scrollRows + .rangeToRegionPoints(startIndex, endIndex, true); return currentPos[1].length; } - final line = _getColumnVisibleLineInfo(index); + final line = _getRowVisibleLineInfo(startIndex); if (line == null) { return 0; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/row_generator.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/row_generator.dart index fb8b0eea3..0533d5177 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/row_generator.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/row_generator.dart @@ -147,8 +147,9 @@ class _RowGenerator { DataRowBase _createHeaderRow( int rowIndex, _VisibleLinesCollection visibleColumns) { final _DataGridSettings dataGridSettings = dataGridStateDetails(); + DataRowBase dr; if (rowIndex == _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - final dr = DataRow() + dr = DataRow() .._dataGridStateDetails = dataGridStateDetails ..rowIndex = rowIndex ..rowRegion = RowRegion.header @@ -157,12 +158,34 @@ class _RowGenerator { .._key = ObjectKey(dr) .._initializeDataRow(visibleColumns); return dr; + } else if (dataGridSettings.stackedHeaderRows != null && + rowIndex < dataGridSettings.stackedHeaderRows.length) { + dr = _SpannedDataRow(); + dr._key = ObjectKey(dr); + dr.rowIndex = rowIndex; + dr._dataGridStateDetails = dataGridStateDetails; + dr.rowRegion = RowRegion.header; + dr.rowType = RowType.stackedHeaderRow; + _createStackedHeaderCell( + dataGridSettings.stackedHeaderRows[rowIndex], rowIndex); + dr._initializeDataRow(visibleColumns); + return dr; } else { return _createDataRow(rowIndex, visibleColumns, rowRegion: RowRegion.header); } } + void _createStackedHeaderCell(StackedHeaderRow header, int rowIndex) { + final _DataGridSettings dataGridSettings = dataGridStateDetails(); + for (final column in header.cells) { + final List childSequence = _StackedHeaderHelper._getChildSequence( + dataGridSettings, column, rowIndex); + childSequence.sort(); + column._childColumnIndexes = childSequence; + } + } + DataRowBase _createFooterRow( int rowIndex, _VisibleLinesCollection visibleColumns) { return _createDataRow(rowIndex, visibleColumns, @@ -181,9 +204,10 @@ class _RowGenerator { void _updateRow(List rows, int index, RowRegion region) { if (region == RowRegion.header) { + DataRowBase dr; final _DataGridSettings dataGridSettings = dataGridStateDetails(); if (index == _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - var dr = rows.firstWhere((row) => row.rowType == RowType.headerRow, + dr = rows.firstWhere((row) => row.rowType == RowType.headerRow, orElse: () => null); if (dr != null) { dr @@ -200,6 +224,26 @@ class _RowGenerator { items.add(dr); dr = null; } + } else if (index < dataGridSettings.stackedHeaderRows.length) { + dr = rows.firstWhere((r) => r.rowType == RowType.stackedHeaderRow, + orElse: () => null); + if (dr != null) { + dr._key = dr._key; + dr.rowIndex = index; + _dataGridStateDetails = dataGridStateDetails; + dr.rowRegion = RowRegion.header; + dr.rowType = RowType.stackedHeaderRow; + dr._rowIndexChanged(); + _createStackedHeaderCell( + dataGridSettings.stackedHeaderRows[index], index); + dr._initializeDataRow( + dataGridSettings.container.scrollRows.getVisibleLines()); + } else { + dr = _createHeaderRow( + index, _SfDataGridHelper.getVisibleLines(dataGridStateDetails())); + items.add(dr); + dr = null; + } } else { _updateDataRow(rows, index, region); } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/spanned_data_row.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/spanned_data_row.dart new file mode 100644 index 000000000..b35591d4c --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/spanned_data_row.dart @@ -0,0 +1,165 @@ +part of datagrid; + +/// Provides functionality to process the spanned data row. +class _SpannedDataRow extends DataRow { + @override + void _onGenerateVisibleColumns(_VisibleLinesCollection visibleColumnLines) { + if (visibleColumnLines.isEmpty) { + return; + } + _visibleColumns.clear(); + + _SpannedDataColumn dc; + final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + if (rowType == RowType.stackedHeaderRow) { + if (dataGridSettings.stackedHeaderRows.isNotEmpty) { + final stackedColumns = + dataGridSettings.stackedHeaderRows[rowIndex].cells; + for (final column in stackedColumns) { + final List> columnsSequence = + _StackedHeaderHelper._getConsecutiveRanges( + column._childColumnIndexes); + + for (final columns in columnsSequence) { + final columnIndex = columns.reduce(min); + dc = _createStackedHeaderColumn( + columnIndex, columns.length - 1, column); + _visibleColumns.add(dc); + } + } + } + } + } + + _SpannedDataColumn _createStackedHeaderColumn( + int index, int columnSpan, StackedHeaderCell stackedHeaderCell) { + final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + final gridColumn = dataGridSettings.columns[index]; + final rowSpan = _StackedHeaderHelper._getRowSpan( + dataGridSettings, rowIndex, index, true, + stackedHeaderCell: stackedHeaderCell); + final dc = _SpannedDataColumn() + .._dataRow = this + ..columnIndex = index + ..rowIndex = rowIndex; + dc._key = ObjectKey(dc); + dc + .._cellType = CellType.stackedHeaderCell + .._stackedHeaderCell = stackedHeaderCell + .._columnSpan = columnSpan + .._rowSpan = rowSpan + ..gridColumn = gridColumn + .._isEnsured = true; + + if (rowType == RowType.stackedHeaderRow) { + dc._renderer = dataGridSettings.cellRenderers['StackedHeader']; + } + dc._columnElement = dc._onInitializeColumnElement(false); + return dc; + } + + @override + void _ensureColumns(_VisibleLinesCollection visibleColumnLines) { + if (rowIndex == -1) { + return; + } + + final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + if (dataGridSettings.stackedHeaderRows.isNotEmpty) { + final stackedHeaderRow = dataGridSettings.stackedHeaderRows[rowIndex]; + final stackedColumns = stackedHeaderRow.cells; + dataGridSettings.rowGenerator + ._createStackedHeaderCell(stackedHeaderRow, rowIndex); + for (final column in stackedColumns) { + final List> columnsSequence = + _StackedHeaderHelper._getConsecutiveRanges( + column._childColumnIndexes); + + for (final columns in columnsSequence) { + final acutalColumnIndex = columns.reduce(min); + var dc = _indexer(acutalColumnIndex); + + if (dc == null) { + var dataCell = _reUseCell( + acutalColumnIndex, acutalColumnIndex + columns.length - 1); + dataCell ??= _visibleColumns.firstWhere( + (col) => + col.columnIndex == -1 && + col._cellType != CellType.indentCell, + orElse: () => null); + + _updateStackedHeaderColumn( + dataCell, acutalColumnIndex, columns.length - 1, column); + dataCell = null; + } + + dc ??= _visibleColumns.firstWhere( + (col) => col.columnIndex == acutalColumnIndex, + orElse: () => null); + + if (dc != null) { + if (!dc._isVisible) { + dc._isVisible = true; + } + } else { + dc = _createStackedHeaderColumn( + acutalColumnIndex, columns.toList().length - 1, column); + _visibleColumns.add(dc); + } + + dc._isEnsured = true; + dc = null; + } + } + } + + for (final col in _visibleColumns) { + if (!col._isEnsured || col.columnIndex == -1) { + col._isVisible = false; + } + } + } + + void _updateStackedHeaderColumn(DataCellBase dc, int index, int columnSpan, + StackedHeaderCell stackedHeaderCell) { + final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + if (dc != null) { + if (index < 0 || index >= dataGridSettings.container.columnCount) { + dc._isVisible = false; + } else { + final columnIndex = _GridIndexResolver.resolveToGridVisibleColumnIndex( + dataGridSettings, index); + final gridColumn = dataGridSettings.columns[columnIndex]; + final rowSpan = _StackedHeaderHelper._getRowSpan( + dataGridSettings, rowIndex, index, true, + stackedHeaderCell: stackedHeaderCell); + dc + ..columnIndex = index + ..rowIndex = rowIndex + .._stackedHeaderCell = stackedHeaderCell + .._cellType = CellType.stackedHeaderCell + .._columnSpan = columnSpan + .._rowSpan = rowSpan + .._key = dc._key + .._isVisible = true; + dc.gridColumn = gridColumn; + if (rowType == RowType.stackedHeaderRow) { + dc._renderer = dataGridSettings.cellRenderers['StackedHeader']; + } + dc + .._columnElement = dc._onInitializeColumnElement(false) + .._isEnsured = true; + } + + if (dc._isVisible != true) { + dc._isVisible = true; + } + } else { + dc = _createStackedHeaderColumn(index, columnSpan, stackedHeaderCell); + _visibleColumns.add(dc); + } + } +} + +/// Provides functionality to display the spanned cell. +class _SpannedDataColumn extends DataCell {} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/enums.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/helper/enums.dart index 96e9a752d..2307207f9 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/enums.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/helper/enums.dart @@ -22,6 +22,9 @@ enum RowType { /// Specifies the data row that displays the row data. dataRow, + + /// Specifies the StackedHeaderRow that displays the stacked header text. + stackedHeaderRow, } /// Describes the possible values for cell types. @@ -37,6 +40,9 @@ enum CellType { /// Specifies the row header cell. rowHeaderCell, + + /// Specifies the stacked header cell. + stackedHeaderCell, } /// Determines how border lines should be shown in [SfDataGrid]. diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/grid_index_resolver.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/helper/grid_index_resolver.dart index c3bff8c65..b0ad3e552 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/grid_index_resolver.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/helper/grid_index_resolver.dart @@ -31,10 +31,7 @@ class _GridIndexResolver { rowIndex = rowIndex + _GridIndexResolver.resolveStartIndexBasedOnPosition(dataGridSettings); - if (rowIndex >= 0 && - rowIndex <= - dataGridSettings.container.rowCount - - dataGridSettings.headerLineCount) { + if (rowIndex >= 0 && rowIndex <= dataGridSettings.container.rowCount) { return rowIndex; } else { return -1; @@ -110,7 +107,7 @@ class _GridIndexResolver { } final rowIndex = dataGridSettings.container.frozenRows - - (dataGridSettings.headerLineCount * 2); + (dataGridSettings.headerLineCount + 1); return _GridIndexResolver.resolveToRowIndex(dataGridSettings, rowIndex); } @@ -126,3 +123,102 @@ class _GridIndexResolver { return _GridIndexResolver.resolveToRowIndex(dataGridSettings, rowIndex); } } + +@protected +class _StackedHeaderHelper { + static List _getChildSequence(_DataGridSettings dataGridSettings, + StackedHeaderCell column, int rowIndex) { + final List childSequenceNo = []; + + if (column != null && + (column.columnNames != null || column.columnNames.isNotEmpty)) { + final childColumns = column.columnNames; + for (final child in childColumns) { + final columns = dataGridSettings.columns; + for (int i = 0; i < columns.length; ++i) { + if (columns[i].mappingName == child) { + childSequenceNo.add(i); + break; + } + } + } + } + return childSequenceNo; + } + + static int _getRowSpan(_DataGridSettings dataGridSettings, int rowindex, + int columnIndex, bool isStackedHeader, + {String mappingName, StackedHeaderCell stackedHeaderCell}) { + int rowSpan = 0; + int startIndex = 0; + int endIndex = 0; + if (isStackedHeader) { + final List> spannedColumns = + _getConsecutiveRanges(stackedHeaderCell._childColumnIndexes); + final List spannedColumn = spannedColumns.singleWhere( + (element) => element.first == columnIndex, + orElse: () => null); + if (spannedColumn != null) { + startIndex = spannedColumn.reduce(min); + endIndex = startIndex + spannedColumn.length - 1; + } + rowindex = rowindex - 1; + } else { + if (rowindex >= dataGridSettings.stackedHeaderRows.length) { + return rowSpan; + } + } + + while (rowindex >= 0) { + final stackedHeaderRow = dataGridSettings.stackedHeaderRows[rowindex]; + for (final stackedColumn in stackedHeaderRow.cells) { + if (stackedColumn != null && isStackedHeader) { + final List> columnsRange = + _getConsecutiveRanges(stackedColumn._childColumnIndexes); + for (final column in columnsRange) { + if ((startIndex >= column.first && startIndex <= column.last) || + (endIndex >= column.first && endIndex <= column.last)) { + return rowSpan; + } + } + } else if (stackedColumn != null && + (stackedColumn.columnNames != null || + stackedColumn.columnNames.isNotEmpty)) { + final children = stackedColumn.columnNames; + for (int child = 0; child < children.length; child++) { + if (children[child] == mappingName) { + return rowSpan; + } + } + } + } + + rowSpan += 1; + rowindex -= 1; + } + + return rowSpan; + } + + static List> _getConsecutiveRanges(List columnsIndex) { + int endIndex = 1; + final List> list = []; + if (columnsIndex.isEmpty) { + return list; + } + for (int i = 1; i <= columnsIndex.length; i++) { + if (i == columnsIndex.length || + columnsIndex[i] - columnsIndex[i - 1] != 1) { + if (endIndex == 1) { + list.add(columnsIndex.sublist(i - endIndex, (i - endIndex) + 1)); + } else { + list.add(columnsIndex.sublist(i - endIndex, i)); + } + endIndex = 1; + } else { + endIndex++; + } + } + return list; + } +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/column_sizer.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/column_sizer.dart index 7c6afd8e2..1f5e02ade 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/column_sizer.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/column_sizer.dart @@ -322,7 +322,7 @@ class ColumnSizer { columns.remove(column); for (final removedColumn in removedColumns) { if (!columns.contains(removedColumn)) { - removedWidth += removedColumn.actualWidth; + removedWidth += removedColumn._actualWidth; columns.add(removedColumn); } } @@ -474,7 +474,7 @@ class ColumnSizer { {bool setWidth = true}) { if (setWidth) { column._actualWidth = _getCellWidth(column); - return column.actualWidth; + return column._actualWidth; } else { return _getCellWidth(column); } @@ -485,14 +485,14 @@ class ColumnSizer { final columnIndex = dataGridSettings.columns.indexOf(column); final width = _getColumnWidth(column, columnWidth); column._actualWidth = width; - dataGridSettings.container.columnWidths[columnIndex] = column.actualWidth; + dataGridSettings.container.columnWidths[columnIndex] = column._actualWidth; return width; } double _getColumnWidth(GridColumn column, double columnWidth) { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); final columnIndex = dataGridSettings.columns.indexOf(column); - if (column.width < column.actualWidth) { + if (column.width < column._actualWidth) { return columnWidth; } @@ -554,10 +554,10 @@ class ColumnSizer { dataGridSettings.container.scrollRows.getVisibleLines(); firstVisibleIndex = visibleLines.firstBodyVisibleIndex <= visibleLines.length - 1 - ? visibleLines[visibleLines.firstBodyVisibleIndex].lineIndex - 1 + ? visibleLines[visibleLines.firstBodyVisibleIndex].lineIndex : 0; lastVisibleIndex = - dataGridSettings.container.scrollRows.lastBodyVisibleLineIndex - 1; + dataGridSettings.container.scrollRows.lastBodyVisibleLineIndex; } else { firstVisibleIndex = 0; lastVisibleIndex = @@ -577,8 +577,11 @@ class ColumnSizer { resultWidth = textWidth; } } else { - final currentRowIndex = - _GridIndexResolver.resolveToRecordIndex(dataGridSettings, rowIndex); + final currentRowIndex = (dataGridSettings.columnWidthCalculationRange == + ColumnWidthCalculationRange.visibleRows) + ? _GridIndexResolver.resolveToRecordIndex( + dataGridSettings, rowIndex) + : rowIndex; final String text = _getDisplayText(currentRowIndex, column); if (text.length >= stringLength) { stringLength = text.length; @@ -601,14 +604,16 @@ class ColumnSizer { double getCellWidth(GridColumn column, int rowIndex) { double textWidth = 0.0; final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final currentRowIndex = - _GridIndexResolver.resolveToRecordIndex(dataGridSettings, rowIndex); + final currentRowIndex = (dataGridSettings.columnWidthCalculationRange == + ColumnWidthCalculationRange.visibleRows) + ? _GridIndexResolver.resolveToRecordIndex(dataGridSettings, rowIndex) + : rowIndex; final formattedText = _getDisplayText(currentRowIndex, column); if (formattedText != null && formattedText.length >= _textLength || - _previousColumnWidth >= column.actualWidth) { + _previousColumnWidth >= column._actualWidth) { textWidth = _measureTextWidth(formattedText, column, rowIndex); _textLength = formattedText.length; - _previousColumnWidth = column.actualWidth; + _previousColumnWidth = column._actualWidth; } return textWidth.roundToDouble(); @@ -628,10 +633,10 @@ class ColumnSizer { _GridIndexResolver.resolveToRecordIndex(dataGridSettings, rowIndex); final formattedText = _getDisplayText(currentRowIndex, column); if (formattedText != null && formattedText.length >= _textLength || - _previousColumnWidth >= column.actualWidth) { + _previousColumnWidth >= column._actualWidth) { textHeight = _measureTextHeight(formattedText, column, rowIndex); _textLength = formattedText.length; - _previousColumnWidth = column.actualWidth; + _previousColumnWidth = column._actualWidth; } return textHeight.roundToDouble(); @@ -695,6 +700,11 @@ class ColumnSizer { int stringLength = 0; final isInDefaultMode = dataGridSettings.columnWidthCalculationMode == ColumnWidthCalculationMode.textSize; + if (dataGridSettings.stackedHeaderRows.isNotEmpty && + rowIndex <= dataGridSettings.stackedHeaderRows.length - 1) { + return dataGridSettings.headerRowHeight; + } + for (int columnIndex = 0; columnIndex < dataGridSettings.columns.length; columnIndex++) { @@ -784,6 +794,32 @@ class ColumnSizer { return cellTextStyle; } + EdgeInsetsGeometry _getPadding(int rowIndex, GridColumn column) { + final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + + if (rowIndex == _GridIndexResolver.getHeaderIndex(dataGridSettings)) { + if (column.headerPadding != null) { + return column.headerPadding; + } else { + final visualDensityPadding = + dataGridSettings.visualDensity.vertical * 2; + return EdgeInsets.all(16) + + EdgeInsets.fromLTRB( + 0.0, visualDensityPadding, 0.0, visualDensityPadding); + } + } else { + if (column.padding != null) { + return column.padding; + } else { + final visualDensityPadding = + dataGridSettings.visualDensity.vertical * 2; + return EdgeInsets.all(16) + + EdgeInsets.fromLTRB( + 0.0, visualDensityPadding, 0.0, visualDensityPadding); + } + } + } + double _measureTextWidth( String displayText, GridColumn column, int rowIndex) { final cellTextStyle = _getCellTextStyle(rowIndex, column); @@ -805,10 +841,12 @@ class ColumnSizer { final gridLinesVisibility = dataGridSettings.gridLinesVisibility; final Size gridLineStrokeWidthSize = _getGridLineStrokeWidthSize(gridLinesVisibility, rowIndex, columnIndex); + + final padding = _getPadding(rowIndex, column); // Removing padding and stroke width values also to the column hight calculation final double actualColumnWidth = columnWidth - _getSortIconWidth(column) - - (column.padding.horizontal + gridLineStrokeWidthSize.width); + (padding.horizontal + gridLineStrokeWidthSize.width); return _calculateTextSize( column, displayText, cellTextStyle, actualColumnWidth, rowIndex) .height; @@ -882,10 +920,10 @@ class ColumnSizer { textDirection: TextDirection.ltr) ..layout(maxWidth: width); textSize = textPainter.size; - - if (column.padding != null) { - textSize = Size(textSize.width + column.padding.horizontal, - textSize.height + column.padding.vertical); + final padding = _getPadding(rowIndex, column); + if (padding != null) { + textSize = Size(textSize.width + padding.horizontal, + textSize.height + padding.vertical); } return Size(textSize.width + gridLineStrokeWidthSize.width, textSize.height + gridLineStrokeWidthSize.height); diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/grid_column.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/grid_column.dart index 2c09a8588..906626ee6 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/grid_column.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/grid_column.dart @@ -3,7 +3,37 @@ part of datagrid; /// Provides the base functionalities for all the column types in [SfDataGrid]. class GridColumn { /// Creates the [GridColumn] for [SfDataGrid] widget. - GridColumn() { + GridColumn( + {@required this.mappingName, + ColumnWidthMode columnWidthMode, + Alignment textAlignment, + Alignment headerTextAlignment, + bool softWrap, + TextOverflow headerTextOverflow, + bool headerTextSoftWrap, + bool visible, + bool allowSorting, + double minimumWidth, + double maximumWidth, + double width, + this.maxLines, + this.overflow, + this.headerText, + this.headerStyle, + this.cellStyle, + this.padding, + this.headerPadding}) + : columnWidthMode = columnWidthMode ?? ColumnWidthMode.none, + textAlignment = textAlignment ?? Alignment.centerLeft, + headerTextAlignment = headerTextAlignment ?? Alignment.center, + softWrap = softWrap ?? false, + headerTextOverflow = headerTextOverflow ?? TextOverflow.ellipsis, + headerTextSoftWrap = headerTextSoftWrap ?? false, + visible = visible ?? true, + allowSorting = allowSorting ?? true, + minimumWidth = minimumWidth ?? double.nan, + maximumWidth = maximumWidth ?? double.nan, + width = width ?? double.nan { _actualWidth = double.nan; _autoWidth = double.nan; } @@ -17,29 +47,13 @@ class GridColumn { /// Defaults to [ColumnWidthMode.none] /// /// Also refer [ColumnWidthMode] - ColumnWidthMode columnWidthMode = ColumnWidthMode.none; + final ColumnWidthMode columnWidthMode; /// The name to map the data member in the underlying data object of /// datasource. /// /// Defaults to null - String get mappingName => _mappingName; - String _mappingName; - - /// The name to map the data member in the underlying data object of - /// datasource. - /// - /// If [headerText] is not set, [mappingName] will be displayed as text - /// in header cell. - /// - /// Defaults to null - set mappingName(String newValue) { - if (_mappingName == newValue) { - return; - } - - _mappingName = newValue; - } + final String mappingName; /// The cell type of the column which denotes renderer associated with column. String get cellType => _cellType; @@ -48,42 +62,41 @@ class GridColumn { /// How the text in cells except header cell is aligned. /// /// Defaults to null - Alignment textAlignment = Alignment.centerLeft; + final Alignment textAlignment; /// How the text in header cell is aligned. /// /// Defaults to null - Alignment headerTextAlignment = Alignment.center; + final Alignment headerTextAlignment; /// An optional maximum number of lines for the text to span, wrapping /// if necessary in cells except header cell. /// /// Defaults to null - int maxLines; + final int maxLines; /// How visual overflow should be handled in cells except header cell. /// /// Defaults to null /// /// Also refer [TextOverflow] - TextOverflow overflow; + final TextOverflow overflow; /// Whether the text should break at soft line breaks. /// /// Defaults to false - bool softWrap = false; + final bool softWrap; /// The actual display width of the column when auto fitted based on /// [SfDataGrid.columnWidthMode] or [columnWidthMode]. /// /// Defaults to [double.nan] - double get actualWidth => _actualWidth; double _actualWidth; /// The text which displays in header cell. /// /// Defaults to null. - String headerText; + final String headerText; /// The minimum width of the column. /// @@ -91,7 +104,7 @@ class GridColumn { /// [minimumWidth] /// /// Defaults to [double.nan] - double minimumWidth = double.nan; + final double minimumWidth; /// The maximum width of the column. /// @@ -99,19 +112,19 @@ class GridColumn { /// [maximumWidth] /// /// Defaults to [double.nan] - double maximumWidth = double.nan; + final double maximumWidth; /// Decides how visual overflow of header text should be handled. /// /// Defaults to Ellipsis /// /// Also refer [TextOverflow] - TextOverflow headerTextOverflow = TextOverflow.ellipsis; + final TextOverflow headerTextOverflow; /// Decides Whether the header text should break at soft line breaks. /// /// Defaults to false - bool headerTextSoftWrap = false; + final bool headerTextSoftWrap; /// The width of the column. /// @@ -121,15 +134,21 @@ class GridColumn { /// [maximumWidth] is set to [width]. /// /// Defaults to [double.nan] - double width = double.nan; + final double width; /// The amount of space between the contents of the cell and the cell's /// border. /// - /// This is also applicable for header cells. + /// This is applicable for grid cells alone. + /// + /// Defaults to 16 + final EdgeInsetsGeometry padding; + + /// The amount of space between the contents of the header cell and the + /// header cell's border. /// /// Defaults to 16 - EdgeInsetsGeometry padding = const EdgeInsets.all(16); + final EdgeInsetsGeometry headerPadding; /// The style of the header cell in the column. /// @@ -142,10 +161,11 @@ class GridColumn { /// body: SfDataGrid( /// source: _employeeDataSource, /// columns: [ - /// GridNumericColumn(mappingName: 'id', headerText: 'ID') - /// ..headerStyle = DataGridHeaderCellStyle( - /// textStyle: - /// TextStyle(color: Colors.red, fontWeight: FontWeight.bold)), + /// GridNumericColumn(mappingName: 'id', headerText: 'ID', + /// headerStyle : DataGridHeaderCellStyle( + /// textStyle: TextStyle( + /// color: Colors.red, + /// fontWeight: FontWeight.bold))), /// GridTextColumn(mappingName: 'name', headerText: 'Name'), /// GridTextColumn(mappingName: 'designation', /// headerText: 'Designation'), @@ -156,7 +176,7 @@ class GridColumn { /// } /// /// ``` - DataGridHeaderCellStyle headerStyle; + final DataGridHeaderCellStyle headerStyle; /// The style of the cells in the column except header cell. /// @@ -169,9 +189,9 @@ class GridColumn { /// body: SfDataGrid( /// source: _employeeDataSource, /// columns: [ - /// GridNumericColumn(mappingName: 'id', headerText: 'ID') - /// ..cellStyle = DataGridCellStyle( - /// textStyle: TextStyle(color: Colors.red)), + /// GridNumericColumn(mappingName: 'id', headerText: 'ID', + /// cellStyle : DataGridCellStyle( + /// textStyle: TextStyle(color: Colors.red))), /// GridTextColumn(mappingName: 'name', headerText: 'Name'), /// GridTextColumn(mappingName: 'designation', /// headerText: 'Designation'), @@ -182,12 +202,12 @@ class GridColumn { /// } /// /// ``` - DataGridCellStyle cellStyle; + final DataGridCellStyle cellStyle; /// Whether column should be hidden. /// /// Defaults to false. - bool visible = true; + final bool visible; /// Decides whether user can sort the column simply by tapping the column /// header. @@ -204,7 +224,7 @@ class GridColumn { /// [SortColumnDetails] objects to sort the columns in [SfDataGrid]. /// * [DataGridSource.sort] - call this method when you are adding the /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. - bool allowSorting = true; + final bool allowSorting; /// Gets the formatted value based on the given format. /// @@ -233,22 +253,55 @@ class GridColumn { /// return SfDataGrid( /// source: employeeDataSource, /// columns: [ -/// GridTextColumn(mappingName: 'name') -/// ..headerText = 'Name', -/// GridTextColumn(mappingName: 'designation') -/// ..headerText = 'Designation', +/// GridTextColumn(mappingName: 'name', headerText : 'Name'), +/// GridTextColumn(mappingName: 'designation', headerText : 'Designation'), /// ], /// ); /// } /// ``` class GridTextColumn extends GridColumn { /// Creates a String column using [mappingName] and [headerText]. - GridTextColumn({@required String mappingName, String headerText}) { - this.mappingName = mappingName; - this.headerText = headerText; + GridTextColumn( + {@required String mappingName, + ColumnWidthMode columnWidthMode, + Alignment textAlignment, + Alignment headerTextAlignment, + bool softWrap, + TextOverflow headerTextOverflow, + EdgeInsetsGeometry padding, + EdgeInsetsGeometry headerPadding, + bool headerTextSoftWrap, + bool visible, + bool allowSorting, + double minimumWidth, + double maximumWidth, + double width, + int maxLines, + TextOverflow overflow, + String headerText, + DataGridHeaderCellStyle headerStyle, + DataGridCellStyle cellStyle}) + : super( + mappingName: mappingName, + columnWidthMode: columnWidthMode, + textAlignment: textAlignment, + headerTextAlignment: headerTextAlignment ?? Alignment.centerLeft, + softWrap: softWrap, + headerTextOverflow: headerTextOverflow, + headerTextSoftWrap: headerTextSoftWrap, + visible: visible, + allowSorting: allowSorting, + minimumWidth: minimumWidth, + maximumWidth: maximumWidth, + width: width, + maxLines: maxLines, + overflow: overflow ?? TextOverflow.ellipsis, + headerText: headerText, + headerStyle: headerStyle, + cellStyle: cellStyle, + padding: padding, + headerPadding: headerPadding) { _cellType = 'TextField'; - headerTextAlignment = Alignment.centerLeft; - overflow = TextOverflow.ellipsis; } } @@ -262,22 +315,56 @@ class GridTextColumn extends GridColumn { /// return SfDataGrid( /// source: employeeDataSource(), /// columns: [ -/// GridNumericColumn(mappingName: 'id') -/// ..headerText = 'ID', -/// GridNumericColumn(mappingName: 'salary') -/// ..headerText = 'Salary', +/// GridNumericColumn(mappingName: 'id', headerText : 'ID'), +/// GridNumericColumn(mappingName: 'salary', headerText : 'Salary'), /// ], /// ); /// } /// ``` class GridNumericColumn extends GridColumn { /// Creates a numeric column using [mappingName] and [headerText]. - GridNumericColumn({@required String mappingName, String headerText}) { - this.mappingName = mappingName; - this.headerText = headerText; + GridNumericColumn({ + @required String mappingName, + this.numberFormat, + ColumnWidthMode columnWidthMode, + Alignment textAlignment, + Alignment headerTextAlignment, + bool softWrap, + TextOverflow headerTextOverflow, + EdgeInsetsGeometry padding, + EdgeInsetsGeometry headerPadding, + bool headerTextSoftWrap, + bool visible, + bool allowSorting, + double minimumWidth, + double maximumWidth, + double width, + int maxLines, + TextOverflow overflow, + String headerText, + DataGridHeaderCellStyle headerStyle, + DataGridCellStyle cellStyle, + }) : super( + mappingName: mappingName, + columnWidthMode: columnWidthMode, + textAlignment: textAlignment ?? Alignment.centerRight, + headerTextAlignment: headerTextAlignment ?? Alignment.centerRight, + softWrap: softWrap, + headerTextOverflow: headerTextOverflow, + headerTextSoftWrap: headerTextSoftWrap, + visible: visible, + allowSorting: allowSorting, + minimumWidth: minimumWidth, + maximumWidth: maximumWidth, + width: width, + maxLines: maxLines, + overflow: overflow, + headerText: headerText, + headerStyle: headerStyle, + cellStyle: cellStyle, + padding: padding, + headerPadding: headerPadding) { _cellType = 'Numeric'; - textAlignment = Alignment.centerRight; - headerTextAlignment = Alignment.centerRight; } /// [intl.NumberFormat] to format a number in a locale-specific way. @@ -295,15 +382,15 @@ class GridNumericColumn extends GridColumn { /// GridTextColumn(mappingName: 'name', headerText: 'Name'), /// GridTextColumn(mappingName: 'designation', /// headerText: 'Designation'), - /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary') - /// ..numberFormat = - /// NumberFormat.currency(locale: 'en_US', symbol: '\$') + /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary', + /// numberFormat : + /// NumberFormat.currency(locale: 'en_US', symbol: '\$')) /// ], /// ), /// ); /// } /// ``` - intl.NumberFormat numberFormat; + final intl.NumberFormat numberFormat; @override String getFormattedValue(Object cellValue) { @@ -329,17 +416,53 @@ class GridNumericColumn extends GridColumn { /// return employees[rowIndex].image; /// }, /// columns: [ -/// GridWidgetColumn(mappingName: image) -/// ..headerText = 'Image', +/// GridWidgetColumn(mappingName: image, headerText : 'Image'), /// ], /// ); /// } /// ``` class GridWidgetColumn extends GridColumn { /// Creates a widget column using [mappingName] and [headerText]. - GridWidgetColumn({@required String mappingName, String headerText}) { - this.mappingName = mappingName; - this.headerText = headerText; + GridWidgetColumn( + {@required String mappingName, + ColumnWidthMode columnWidthMode, + Alignment textAlignment, + Alignment headerTextAlignment, + bool softWrap, + TextOverflow headerTextOverflow, + EdgeInsetsGeometry padding, + EdgeInsetsGeometry headerPadding, + bool headerTextSoftWrap, + bool visible, + bool allowSorting, + double minimumWidth, + double maximumWidth, + double width, + int maxLines, + TextOverflow overflow, + String headerText, + DataGridHeaderCellStyle headerStyle, + DataGridCellStyle cellStyle}) + : super( + mappingName: mappingName, + columnWidthMode: columnWidthMode, + textAlignment: textAlignment, + headerTextAlignment: headerTextAlignment, + softWrap: softWrap, + headerTextOverflow: headerTextOverflow, + headerTextSoftWrap: headerTextSoftWrap, + visible: visible, + allowSorting: allowSorting, + minimumWidth: minimumWidth, + maximumWidth: maximumWidth, + width: width, + maxLines: maxLines, + overflow: overflow, + headerText: headerText, + headerStyle: headerStyle, + cellStyle: cellStyle, + padding: padding, + headerPadding: headerPadding) { _cellType = 'Widget'; } } @@ -354,22 +477,57 @@ class GridWidgetColumn extends GridColumn { /// return SfDataGrid( /// source: employeeDataSource(), /// columns: [ -/// GridNumericColumn(mappingName: 'id') -/// ..headerText = 'ID', -/// GridDateTimeColumn(mappingName: 'dateofjoining') -/// ..headerText = 'Date of Joining', +/// GridNumericColumn(mappingName: 'id', headerText : 'ID'), +/// GridDateTimeColumn(mappingName: 'dateofjoining', +/// headerText : 'Date of Joining'), /// ], /// ); /// } /// ``` class GridDateTimeColumn extends GridColumn { /// Creates a datetime column using [mappingName] and [headerText]. - GridDateTimeColumn({@required String mappingName, String headerText}) { - this.mappingName = mappingName; - this.headerText = headerText; + GridDateTimeColumn( + {@required String mappingName, + this.dateFormat, + ColumnWidthMode columnWidthMode, + Alignment textAlignment, + Alignment headerTextAlignment, + bool softWrap, + TextOverflow headerTextOverflow, + EdgeInsetsGeometry padding, + EdgeInsetsGeometry headerPadding, + bool headerTextSoftWrap, + bool visible, + bool allowSorting, + double minimumWidth, + double maximumWidth, + double width, + int maxLines, + TextOverflow overflow, + String headerText, + DataGridHeaderCellStyle headerStyle, + DataGridCellStyle cellStyle}) + : super( + mappingName: mappingName, + columnWidthMode: columnWidthMode, + textAlignment: textAlignment ?? Alignment.centerRight, + headerTextAlignment: headerTextAlignment ?? Alignment.centerRight, + softWrap: softWrap, + headerTextOverflow: headerTextOverflow, + headerTextSoftWrap: headerTextSoftWrap, + visible: visible, + allowSorting: allowSorting, + minimumWidth: minimumWidth, + maximumWidth: maximumWidth, + width: width, + maxLines: maxLines, + overflow: overflow, + headerText: headerText, + headerStyle: headerStyle, + cellStyle: cellStyle, + padding: padding, + headerPadding: headerPadding) { _cellType = 'DateTime'; - textAlignment = Alignment.centerRight; - headerTextAlignment = Alignment.centerRight; } /// [intl.DateFormat] to format the dates in a locale-sensitive manner. @@ -385,15 +543,15 @@ class GridDateTimeColumn extends GridColumn { /// GridNumericColumn(mappingName: 'id', headerText: 'ID'), /// GridTextColumn(mappingName: 'name', headerText: 'Name'), /// GridDateTimeColumn( - /// mappingName: 'dateOfJoining', headerText: 'Date of Joining') - /// ..dateFormat = DateFormat('dd/MM/yyyy'), + /// mappingName: 'dateOfJoining', headerText: 'Date of Joining', + /// dateFormat : DateFormat('dd/MM/yyyy')), /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary'), /// ], /// ), /// ); /// } /// ``` - intl.DateFormat dateFormat; + final intl.DateFormat dateFormat; final intl.DateFormat _defaultDateFormat = intl.DateFormat('dd-MM-yyyy'); diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/stacked_header.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/stacked_header.dart new file mode 100644 index 000000000..baa22ab0e --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/stacked_header.dart @@ -0,0 +1,53 @@ +part of datagrid; + +/// Row configuration for stacked header in [SfDataGrid]. The columns for this +/// stacked header row are provided in the [stackedHeaderCells] property of the +/// [StackedHeaderRow] object. +/// +/// See also: +/// +/// [StackedHeaderCell] – which provides the configuration for column in stacked +/// header row. +class StackedHeaderRow { + /// Creates the [StackedHeaderRow] for [SfDataGrid] widget. + StackedHeaderRow({List cells}) { + this.cells = cells ?? []; + } + + /// The name of the stacked header row. + /// + /// This is used to identify the header rows uniquely in + /// [stackedHeaderCellBuilder]. Each stacked header row must be named + /// uniquely. + String name; + + /// The collection of [StackedHeaderCell] in stacked header row. + List cells; +} + +/// Column configuration for stacked header row in `SfDataGrid`. +/// +/// See also: +/// +/// [StackedHeaderRow] – which provides configuration for stacked header row. +class StackedHeaderCell { + /// Creates the [StackedHeaderCell] for [StackedHeaderRow]. + StackedHeaderCell({@required this.columnNames, @required this.child}); + + /// The collection of string which is the [GridColumn.mappingName] of the + /// columns defined in the [SfDataGrid]. + /// + /// The columns are spanned as a stacked header based on this collection. If + /// the given collection has the sequence of columns which are presented in + /// the [SfDataGrid], those columns will be spanned. Otherwise, stacked header + /// is added for each column which are not in sequence order in regular + /// columns. + final List columnNames; + + /// The widget that represents the data of this cell. + /// + /// Typically, a [Text] widget. + final Widget child; + + List _childColumnIndexes; +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/scrollview_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/scrollview_widget.dart index 2abd1b49a..9318ebf63 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/scrollview_widget.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/scrollview_widget.dart @@ -20,6 +20,10 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { double _height = 0.0; bool _isScrolling = false; + // This flag is used to restrict the load more view from being shown for + // every moment when datagrid reached at bottom on vertical scrolling. + bool _isLoadMoreViewLoaded = false; + @override void initState() { _verticalController = ScrollController()..addListener(_verticalListener); @@ -64,6 +68,7 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { _container.setRowHeights(); _isScrolling = true; _container._isDirty = true; + _isLoadMoreViewLoaded = false; }); } @@ -142,9 +147,11 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { void _addScrollView(List children) { final double extentWidth = _container.extentWidth; - final double headerRowHeight = _dataGridSettings.container.rowHeights[0]; - final double extentHeight = _container.extentHeight - headerRowHeight; - final double scrollViewHeight = _height - headerRowHeight; + final double headerRowsHeight = _container.scrollRows + .rangeToRegionPoints(0, _dataGridSettings.headerLineCount - 1, true)[1] + .length; + final double extentHeight = _container.extentHeight - headerRowsHeight; + final double scrollViewHeight = _height - headerRowsHeight; final Size containerSize = Size( _canDisableHorizontalScrolling(_dataGridSettings) @@ -157,20 +164,22 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { : scrollViewHeight)); final Widget scrollView = Scrollbar( + isAlwaysShown: _dataGridSettings.isScrollbarAlwaysShown, controller: _horizontalController, child: SingleChildScrollView( controller: _horizontalController, scrollDirection: Axis.horizontal, - physics: const AlwaysScrollableScrollPhysics(), + physics: _dataGridSettings.horizontalScrollPhysics, child: ConstrainedBox( constraints: BoxConstraints( minWidth: min(_width, extentWidth), ), child: Scrollbar( + isAlwaysShown: _dataGridSettings.isScrollbarAlwaysShown, controller: _verticalController, child: SingleChildScrollView( controller: _verticalController, - physics: const AlwaysScrollableScrollPhysics(), + physics: _dataGridSettings.verticalScrollPhysics, child: ConstrainedBox( constraints: BoxConstraints( minHeight: min(scrollViewHeight, extentHeight), @@ -184,7 +193,7 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { ); final Positioned wrapScrollView = Positioned.fill( - top: headerRowHeight, + top: headerRowsHeight, child: scrollView, ); @@ -196,16 +205,33 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { _canDisableHorizontalScrolling(_dataGridSettings) ? _width : max(_width, _container.extentWidth); - List _buildHeaderRows() => _rowGenerator.items - .where((rows) => - rows.rowRegion == RowRegion.header && - rows.rowType == RowType.headerRow) - .map((dataRow) => _HeaderCellsWidget( - key: dataRow._key, - dataRow: dataRow, - isDirty: _container._isDirty || dataRow._isDirty, - )) - .toList(growable: false); + + List _buildHeaderRows() { + final List headerRows = []; + if (_dataGridSettings.stackedHeaderRows.isNotEmpty) { + headerRows.addAll(_rowGenerator.items + .where((rows) => + rows.rowRegion == RowRegion.header && + rows.rowType == RowType.stackedHeaderRow) + .map((dataRow) => _HeaderCellsWidget( + key: dataRow._key, + dataRow: dataRow, + isDirty: _container._isDirty || dataRow._isDirty, + )) + .toList(growable: false)); + } + headerRows.addAll(_rowGenerator.items + .where((rows) => + rows.rowRegion == RowRegion.header && + rows.rowType == RowType.headerRow) + .map((dataRow) => _HeaderCellsWidget( + key: dataRow._key, + dataRow: dataRow, + isDirty: _container._isDirty || dataRow._isDirty, + )) + .toList(growable: false)); + return headerRows; + } double getStartX() { if (_dataGridSettings.textDirection == TextDirection.ltr) { @@ -228,22 +254,56 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { if (_rowGenerator.items.isNotEmpty) { final List headerRows = _buildHeaderRows(); - for (final row in headerRows) { + for (int i = 0; i < headerRows.length; i++) { + final lineInfo = _container.scrollRows.getVisibleLineAtLineIndex(i); final Positioned header = Positioned.directional( textDirection: _dataGridSettings.textDirection, start: getStartX(), - top: 0.0, - height: _dataGridSettings.container.rowHeights[0], + 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: row); + child: headerRows[i]); children.add(header); } } } + void _addLoadMoreView(List children) { + Future loadMoreRows() async { + _isLoadMoreViewLoaded = true; + await _dataGridSettings.source.handleLoadMoreRows(); + } + + if (_verticalController.hasClients && + _dataGridSettings.loadMoreViewBuilder != null) { + if (_verticalController.offset >= + _verticalController.position.maxScrollExtent && + !_isLoadMoreViewLoaded) { + final loadMoreView = + _dataGridSettings.loadMoreViewBuilder(context, loadMoreRows); + + if (loadMoreView != null) { + final loadMoreAlignment = + _dataGridSettings.textDirection == TextDirection.ltr + ? Alignment.bottomLeft + : Alignment.bottomRight; + + children.add(Positioned( + top: 0.0, + width: _width, + height: _height, + child: Align( + alignment: loadMoreAlignment, + child: loadMoreView, + ))); + } + } + } + } + void _handleSelectionController() async { setState(() { /* Rebuild the DataGrid when the selection or currentcell is processed. */ @@ -353,6 +413,8 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { _addScrollView(children); + _addLoadMoreView(children); + _container._isDirty = false; _isScrolling = false; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_helper.dart index 1f39027f0..1eaa81cb7 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_helper.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_helper.dart @@ -386,7 +386,15 @@ class _VisualContainerHelper { } void _refreshHeaderLineCount() { - dataGridStateDetails().headerLineCount = 1; + final dataGridSettings = dataGridStateDetails(); + _headerLineCount = 1; + if (dataGridSettings.stackedHeaderRows != null && + dataGridSettings.stackedHeaderRows.isNotEmpty) { + _headerLineCount += dataGridSettings.stackedHeaderRows.length; + dataGridSettings.headerLineCount = _headerLineCount; + } else { + dataGridSettings.headerLineCount = 1; + } } void _updateRowAndColumnCount() { diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/current_cell_manager.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/current_cell_manager.dart index 638f3a6e0..9437843dc 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/current_cell_manager.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/current_cell_manager.dart @@ -225,7 +225,7 @@ class _CurrentCellManager { void _updateBorderForMultipleSelection(_DataGridSettings dataGridSettings, {RowColumnIndex previousRowColumnIndex, RowColumnIndex nextRowColumnIndex}) { - if (kIsWeb && + if (dataGridSettings._isDesktop && dataGridSettings.navigationMode == GridNavigationMode.row && dataGridSettings.selectionMode == SelectionMode.multiple) { if (previousRowColumnIndex != null) { diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/row_selection_manager.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/row_selection_manager.dart index 547b6588e..99eb45433 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/row_selection_manager.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/row_selection_manager.dart @@ -501,7 +501,7 @@ class RowSelectionManager extends SelectionManagerBase { _selectedRows.selectedRow.isNotEmpty) { final lastRecord = _selectedRows.selectedRow.last; _addCurrentCell(lastRecord, dataGridSettings, isSelectionChanging: true); - } else if (kIsWeb && + } else if (dataGridSettings._isDesktop && dataGridSettings.navigationMode == GridNavigationMode.row) { final lastRecord = _selectedRows.selectedRow.last; final rowIndex = @@ -550,7 +550,7 @@ class RowSelectionManager extends SelectionManagerBase { if (lastRecord != null) { _addSelection(lastRecord, dataGridSettings); } - } else if (kIsWeb && + } else if (dataGridSettings._isDesktop && dataGridSettings.selectionMode == SelectionMode.multiple) { final currentRowColumnIndex = RowColumnIndex(dataGridSettings.currentCell.rowIndex, null); diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_manager_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_manager_base.dart index 02d443220..b5299ff14 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_manager_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_manager_base.dart @@ -82,8 +82,7 @@ class SelectionManagerBase extends ChangeNotifier { } int _getPreviousRowIndex( - _DataGridSettings dataGridSettings, int currentRowIndex, - {bool isSelection = false}) { + _DataGridSettings dataGridSettings, int currentRowIndex) { final lastRowIndex = _SelectionHelper.getLastNavigatingRowIndex(dataGridSettings); if (currentRowIndex > lastRowIndex) { @@ -117,7 +116,7 @@ class SelectionManagerBase extends ChangeNotifier { var gridColumn = dataGridSettings.columns[resolvedIndex]; if (gridColumn == null || !gridColumn.visible || - gridColumn.actualWidth == 0.0) { + gridColumn._actualWidth == 0.0) { gridColumn = _getNextGridColumn(dataGridSettings, moveToRight ? columnIndex + 1 : columnIndex - 1, moveToRight); } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart b/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart index eb42976c8..7e1f896cc 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart @@ -5,6 +5,12 @@ typedef DataPagerItemBuilderCallback = Widget Function(String text); typedef _DataPagerControlListener = void Function({String property}); +/// Signature for the builder callback used by [SfDataGrid.onPageNavigationStart]. +typedef PageNavigationStart = void Function(int pageIndex); + +/// Signature for the [SfDataGrid.onPageNavigationEnd] callback. +typedef PageNavigationEnd = void Function(int pageIndex); + // Its used to suspend the data pager update more than once when using the // data pager with data grid. bool _suspendDataPagerUpdate = false; @@ -65,22 +71,21 @@ bool _suspendDataPagerUpdate = false; /// [SfDataGrid] /// /// ```dart -/// class  PaginatedDataGridSource extends DataGridSource { +/// class  PaginatedDataGridSource extends DataGridSource { ///  @override -///  List get dataSource => _paginatedData; +///  List get dataSource => _paginatedData; /// ///  @override -///  Object getCellValue(int rowIndex, String columnName) { -///    final EmployeeData data = dataSource[rowIndex]; +///  Object getValue(Employee employee, String columnName) { ///    switch (columnName) { ///      case 'designation': -///        return data.designation; +///        return employee.designation; ///        break; ///      case 'salary': -///        return data.salary; +///        return employee.salary; ///        break; ///      case 'employeeName': -///        return data.employeeName; +///        return employee.employeeName; ///      default: ///        return ''; ///        break; @@ -108,6 +113,14 @@ class DataPagerController extends _DataPagerChangeNotifier { int get selectedPageIndex => _selectedPageIndex; int _selectedPageIndex = 0; + set selectedPageIndex(int newSelectedPageIndex) { + if (_selectedPageIndex == newSelectedPageIndex) { + return; + } + _selectedPageIndex = newSelectedPageIndex; + _notifyDataPagerListeners('selectedPageIndex'); + } + /// The total number of pages in the data pager. int get pageCount => _pageCount; int _pageCount; @@ -251,22 +264,21 @@ class DataPagerDelegate { /// to [SfDataGrid] /// /// ```dart -/// class  PaginatedDataGridSource extends DataGridSource { +/// class  PaginatedDataGridSource extends DataGridSource { ///  @override -///  List get dataSource => _paginatedData; +///  List get dataSource => _paginatedData; /// ///  @override -/// Object getCellValue(int rowIndex, String columnName) { -///    final EmployeeData data = dataSource[rowIndex]; +/// Object getValue(Employee employee, String columnName) { ///    switch (columnName) { ///      case 'designation': -///        return data.designation; +///        return employee.designation; ///        break; ///      case 'salary': -///        return data.salary; +///        return employee.salary; ///        break; ///      case 'employeeName': -///        return data.employeeName; +///        return employee.employeeName; ///      default: ///        return ''; ///        break; @@ -299,6 +311,8 @@ class SfDataPager extends StatefulWidget { this.visibleItemsCount = 5, this.initialPageIndex = 0, this.pageItemBuilder, + this.onPageNavigationStart, + this.onPageNavigationEnd, this.controller}) : assert(rowsPerPage != null), assert(delegate != null); @@ -341,6 +355,14 @@ class SfDataPager extends StatefulWidget { /// scrolled. final DataPagerController controller; + /// Called when page is being navigated. + /// Typically you can use this callback to call the setState() to display the loading indicator while retrieving the rows from services. + final PageNavigationStart onPageNavigationStart; + + /// Called when page is successfully navigated. + /// Typically you can use this callback to call the setState() to hide the loading indicator once data is successfully retrieved from services. + final PageNavigationEnd onPageNavigationEnd; + @override _SfDataPagerState createState() => _SfDataPagerState(); @@ -370,7 +392,8 @@ class _SfDataPagerState extends State { static const double _defaultPageItemWidth = 50.0; static const double _defaultPageItemHeight = 50.0; static const EdgeInsets _defaultPageItemPadding = EdgeInsets.all(5); - static const Size _defaultPagerDimension = Size(300.0, 50.0); + static const Size _defaultPagerDimension = + Size(300.0, _defaultPageItemHeight); static const Size _defaultPagerLabelDimension = Size(200.0, 50.0); static const double _kMobileViewWidthOnWeb = 767.0; @@ -396,6 +419,7 @@ class _SfDataPagerState extends State { bool _isDirty = false; bool _isOrientationChanged = false; bool _isPregenerateItems = false; + bool _isDesktop; bool get _isRTL => _textDirection == TextDirection.rtl; @@ -491,6 +515,7 @@ class _SfDataPagerState extends State { _isDirty = true; }); } + _raisePageNavigationEnd(canChange ? index : _currentPageIndex); _suspendDataPagerUpdate = false; } @@ -508,6 +533,7 @@ class _SfDataPagerState extends State { await _scrollTo(_scrollController.position.minScrollExtent); _setCurrentPageIndex(0); } + _raisePageNavigationEnd(canChangePage ? 0 : _currentPageIndex); break; case 'last': if (_lastPageIndex <= 0) { @@ -519,7 +545,8 @@ class _SfDataPagerState extends State { await _scrollTo(_scrollController.position.maxScrollExtent); _setCurrentPageIndex(_lastPageIndex); } - + _raisePageNavigationEnd( + canChangePage ? _lastPageIndex : _currentPageIndex); break; case 'previous': final previousIndex = _getPreviousPageIndex(); @@ -533,7 +560,8 @@ class _SfDataPagerState extends State { _moveToPreviousPage(); _setCurrentPageIndex(previousIndex); } - + _raisePageNavigationEnd( + canChangePage ? previousIndex : _currentPageIndex); break; case 'next': final nextPageIndex = _getNextPageIndex(); @@ -548,7 +576,8 @@ class _SfDataPagerState extends State { _moveToNextPage(); _setCurrentPageIndex(nextPageIndex); } - + _raisePageNavigationEnd( + canChangePage ? nextPageIndex : _currentPageIndex); break; case 'initialPageIndex': if (widget.initialPageIndex != null && @@ -563,10 +592,30 @@ class _SfDataPagerState extends State { await _scrollTo(distance, canUpdate: true); _setCurrentPageIndex(index); } + _raisePageNavigationEnd(canChangePage ? index : _currentPageIndex); break; case 'pageCount': + _currentPageIndex = 0; + await _scrollTo(0); _handleScrollPositionChanged(); break; + case 'selectedPageIndex': + final selectedPageIndex = _controller.selectedPageIndex; + if (selectedPageIndex < 0 || + selectedPageIndex > _lastPageIndex || + selectedPageIndex == _currentPageIndex) { + return; + } + final bool canChangePage = await _canChangePage(selectedPageIndex); + + if (canChangePage) { + final double distance = getScrollOffset(selectedPageIndex); + await _scrollTo(distance); + _setCurrentPageIndex(selectedPageIndex); + } + _raisePageNavigationEnd( + canChangePage ? selectedPageIndex : _currentPageIndex); + break; default: break; } @@ -574,6 +623,8 @@ class _SfDataPagerState extends State { } Future _canChangePage(int index) async { + _raisePageNavigationStart(index); + final bool canHandle = await widget.delegate.handlePageChange( _currentPageIndex, index, @@ -584,6 +635,22 @@ class _SfDataPagerState extends State { return canHandle; } + void _raisePageNavigationStart(int pageIndex) { + if (widget.onPageNavigationStart == null) { + return null; + } + + widget.onPageNavigationStart(pageIndex); + } + + void _raisePageNavigationEnd(int pageIndex) { + if (widget.onPageNavigationEnd == null) { + return null; + } + + widget.onPageNavigationEnd(pageIndex); + } + // ScrollController helpers double _getCumulativeSize(int index) { @@ -600,37 +667,31 @@ class _SfDataPagerState extends State { return previousPageIndex.isNegative ? -1 : previousPageIndex; } - double _getNextIndexOffset() { - final int nextIndex = _getNextPageIndex(); - final double origin = _getCumulativeSize(nextIndex); - final double corner = origin + _getButtonSize(); + double getScrollOffset(int index) { + final double origin = _getCumulativeSize(index); final double scrollOffset = _scrollController.offset; + final double corner = origin + _getButtonSize(); final double currentViewSize = scrollOffset + _scrollViewPortSize; - - final double offset = (corner > currentViewSize) - ? scrollOffset + (corner - currentViewSize) - : scrollOffset; - return offset; - } - - double _getPreviousIndexOffset() { - final previousIndex = _getPreviousPageIndex(); - final double origin = _getCumulativeSize(previousIndex); - final double scrollOffset = _scrollController.offset; - - final double offset = (origin < scrollOffset) - ? scrollOffset - (scrollOffset - origin) - : scrollOffset; + double offset = 0; + if (corner > currentViewSize) { + offset = scrollOffset + (corner - currentViewSize); + } else if (origin < scrollOffset) { + offset = scrollOffset - (scrollOffset - origin); + } else { + offset = scrollOffset; + } return offset; } void _moveToNextPage() { - final distance = _getNextIndexOffset(); + final nextIndex = _getNextPageIndex(); + final double distance = getScrollOffset(nextIndex); _scrollTo(distance); } void _moveToPreviousPage() { - final distance = _getPreviousIndexOffset(); + final previousIndex = _getPreviousPageIndex(); + final double distance = getScrollOffset(previousIndex); _scrollTo(distance); } @@ -768,13 +829,13 @@ class _SfDataPagerState extends State { } bool isNavigatorItemVisible(String type) { - if (type == 'next' || type == 'last') { + if (type == 'Next' || type == 'Last') { if (_currentPageIndex == _lastPageIndex) { return true; } } - if (type == 'first' || type == 'previous') { + if (type == 'First' || type == 'Previous') { if (_currentPageIndex == 0) { return true; } @@ -861,18 +922,21 @@ class _SfDataPagerState extends State { _dataPagerThemeData.itemBorderWidth > 0.0 ? Border.all( width: _dataPagerThemeData.itemBorderWidth, - color: visible - ? _dataPagerThemeData.disabledItemColor - : _dataPagerThemeData.itemBorderColor) + color: _dataPagerThemeData.itemBorderColor) : Border.all(width: 0.0, color: Colors.transparent); } if (pagerItem == null) { if (element == null) { visible = !isNavigatorItemVisible(type); - itemColor = _dataPagerThemeData.itemColor; + itemColor = visible + ? _dataPagerThemeData.itemColor + : _dataPagerThemeData.disabledItemColor; - pagerItem = _getIcon(type, iconData, visible); + pagerItem = Semantics( + label: '$type Page', + child: _getIcon(type, iconData, visible), + ); pagerItemKey = ObjectKey(type); } else { final bool isSelected = checkIsSelectedIndex(element.index); @@ -936,7 +1000,8 @@ class _SfDataPagerState extends State { return; } - _handleDataPagerControlPropertyChanged(property: type); + _handleDataPagerControlPropertyChanged( + property: type.toLowerCase()); } }, child: Align( @@ -971,11 +1036,11 @@ class _SfDataPagerState extends State { } //FirstIcon - children.add(_buildDataPagerItem(type: 'first', iconData: getFirstIcon())); + children.add(_buildDataPagerItem(type: 'First', iconData: getFirstIcon())); //PreviousIcon children.add( - _buildDataPagerItem(type: 'previous', iconData: getPreviousIcon())); + _buildDataPagerItem(type: 'Previous', iconData: getPreviousIcon())); //Set headerExtent _headerExtent = children.isEmpty ? 0.0 : children.length * _getButtonSize(); @@ -1004,10 +1069,10 @@ class _SfDataPagerState extends State { } //NextIcon - children.add(_buildDataPagerItem(type: 'next', iconData: getNextIcon())); + children.add(_buildDataPagerItem(type: 'Next', iconData: getNextIcon())); //LastIcon - children.add(_buildDataPagerItem(type: 'last', iconData: getLastIcon())); + children.add(_buildDataPagerItem(type: 'Last', iconData: getLastIcon())); //Set footerExtent _footerExtent = children.isEmpty ? 0.0 : children.length * _getButtonSize(); @@ -1186,8 +1251,12 @@ class _SfDataPagerState extends State { height: _dataPagerConstraint.maxHeight, child: Align( alignment: _isRTL - ? canEnablePagerLabel ? Alignment.centerRight : Alignment.center - : canEnablePagerLabel ? Alignment.centerLeft : Alignment.center, + ? canEnablePagerLabel + ? Alignment.centerRight + : Alignment.center + : canEnablePagerLabel + ? Alignment.centerLeft + : Alignment.center, child: Container( width: _getDataPagerWidth(), height: _getDataPagerHeight(), @@ -1211,6 +1280,10 @@ class _SfDataPagerState extends State { textDirection = Directionality.of(context); dataPagerThemeData = SfDataPagerTheme.of(context); _localization = SfLocalizations.of(context); + final ThemeData themeData = Theme.of(context); + _isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows; } @override @@ -1230,7 +1303,18 @@ class _SfDataPagerState extends State { _addDelegateListener(); } + if (oldWidget.rowsPerPage != widget.rowsPerPage) { + _currentPageIndex = 0; + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _handlePageItemTapped(_currentPageIndex); + }); + } + if (isDataPagerControllerChanged) { + if (_controller.pageCount != widget.controller?.pageCount) { + _currentPageIndex = 0; + } + oldWidget.controller .removeListener(_handleDataPagerControlPropertyChanged); _controller = widget.controller ?? _controller @@ -1250,7 +1334,7 @@ class _SfDataPagerState extends State { builder: (context, constraint) { _updateConstraintChanged(constraint); - if (kIsWeb && widget.direction == Axis.horizontal) { + if (_isDesktop && widget.direction == Axis.horizontal) { final List children = []; _buildDataPagerWithLabel(constraint, children); @@ -1569,7 +1653,7 @@ class _DataPagerChangeNotifier { ObserverList<_DataPagerControlListener>(); void addListener(_DataPagerControlListener listener) { - _listeners.add(listener); + _listeners?.add(listener); } void _notifyDataPagerListeners(String propertyName) { @@ -1579,7 +1663,7 @@ class _DataPagerChangeNotifier { } void removeListener(_DataPagerControlListener listener) { - _listeners.add(listener); + _listeners?.remove(listener); } @mustCallSuper diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/generic_tree_table.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/generic_tree_table.dart deleted file mode 100644 index d25527344..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/generic_tree_table.dart +++ /dev/null @@ -1,411 +0,0 @@ -part of datagrid; - -/// A leaf in the tree with value and optional sort key. -/// -/// * T - _required_ - Tree value -class _GenericTreeTableEntry extends _TreeTableNode - with _TreeTableEntryBase { - /// Initializes a new instance of the GenericTreeTableEntry class. - _GenericTreeTableEntry(); - - /// Initializes a new instance of the GenericTreeTableEntry class. - /// - /// * tree - _required_ - Internal tree - /// * value - _required_ - tree value - _GenericTreeTableEntry.fromGenericTreeTable( - _GenericTreeTable tree, this.thisValue) { - this.tree = tree.internalTree; - } - - /// Initializes a new instance of the GenericTreeTableEntry class. - /// - /// * tree - _required_ - Current tree instance - /// * value - _required_ - Tree value - _GenericTreeTableEntry.fromTreeTable(_TreeTable tree, this.thisValue) { - this.tree = tree; - } - - T thisValue; - - @override - Object get value => thisValue; - - @override - set value(Object value) => thisValue = value; - - /// The Debug / text information about the node. - /// - /// Returns the Debug / text information about the node. - String getNodeInfo() => - '${getNodeInfoBase()} ${value != null ? value.toString() : 'null'}'; - - /// The sort key of this leaf. - /// - /// Returns the sort key of this leaf - T getSortKeyBase() => thisValue; - - /// Creates a branch that can hold this entry when new leaves are inserted - /// into the tree. - /// - /// * tree - _required_ - tree instance - /// - /// Returns the instance of newly created branch - @override - _TreeTableBranchBase createBranch(_TreeTable tree) => _TreeTableBranch(tree); - - /// Indicates whether this is a leaf. - /// - /// Returns boolean value - @override - bool isEntry() => true; - - /// The minimum value (of the most-left leaf) of the branch in a sorted tree. - /// - /// Returns the minimum value (of the most-left leaf) of the branch - /// in a sorted tree. - @override - Object getMinimum() => getSortKey(); - - @override - Object getSortKey() => getSortKeyBase(); - - /// The number of child nodes (+1 for the current node). - /// - /// Returns the number of child nodes (+1 for the current node). - @override - int getCount() => 1; -} - -/// A tree table. -/// -/// * T - _required_ - Generic tree table -class _GenericTreeTable - implements - _TreeTableBase, - _ListGenericBase<_GenericTreeTableEntry>, - _CollectionGenericBase<_GenericTreeTableEntry> { - /// Initializes a new instance of the [GenericTreeTable{T}] class. - /// - /// * sorted - _required_ - The boolean value denotes whether the tree is - /// sorted or not - _GenericTreeTable(bool sorted) { - _thisTree = _TreeTable(sorted)..tag = this; - } - - _TreeTable _thisTree; - - /// Gets the identifier - int get identifier => _identifier; - int _identifier = 0; - set identifier(int value) { - if (value == _identifier) { - return; - } - _identifier = value; - } - - /// Gets the non-generic tree table with actual implementation. - _TreeTable get internalTree => _thisTree; - - /// Gets a value indicating whether the collection has a fixed size. - /// - /// Returns `True` if the collection has a fixed size; otherwise, `false`. - bool get isFixedSizeBase => _thisTree.isFixedSize; - - /// Gets an object that can be used to synchronize access - /// to the [ICollection] - /// - /// Returns an object that can be used to synchronize access - /// to the [ICollection]. - Object get syncRootBase => null; - - /// Gets the tag that can be associated with the tree - Object get tag => _thisTree.tag; - set tag(Object value) => _thisTree.tag = value; - - /// Gets a value indicating whether the tree was initialize or not. - @override - bool get isInitializing => _thisTree.isInitializing; - - /// Gets a value indicating whether the collection has a fixed size. - /// - /// Returns `True` if the collection has a fixed size; otherwise, `false`. - @override - bool get isFixedSize => isFixedSizeBase; - - /// Gets an object that can be used to synchronize access - /// to the [ICollection]. - /// - /// Returns an object that can be used to synchronize access - /// to the [ICollection]. - @override - Object get syncRoot => syncRootBase; - - /// Gets a value indicating whether access to the `ICollection` - /// is synchronized (thread safe). - /// - /// Returns `True` if access to the [ICollection] is synchronized; otherwise, - /// `false`. Returns `false`. - @override - bool get isSynchronized => false; - - /// Gets the number of items contained in the collection. - @override - int get count => _thisTree.getCount(); - - /// Gets a value indicating whether the collection is read-only. - /// - /// Returns true if the collection is read-only; otherwise, false. - @override - bool get isReadOnly => _thisTree.isReadOnly; - - /// Gets the root node. - @override - _TreeTableNodeBase get root => _thisTree.root; - - /// Gets a value indicating whether thisTree is sorted or not. - @override - bool get sorted => _thisTree.sorted; - - /// Gets the comparer used by sorted trees. - @override - Comparable get comparer => _thisTree.comparer; - - @override - set comparer(Comparable value) => _thisTree.comparer = value; - - /// Adds a value to the end of the collection. - /// - /// * item - _required_ - The item to be added to the end of the collection. - /// The value must not be a NULL reference (Nothing in Visual Basic). - /// - /// Returns the zero-based collection index at which the value has been added. - int addBase(_GenericTreeTableEntry item) => _thisTree.add(item); - - /// Add the key if it is not in the collection - /// - /// * key - _required_ - key needs to be add - /// * entry - _required_ - tree value - /// - /// Returns the instance for the tree - _GenericTreeTableEntry addIfNotExists( - Object key, _GenericTreeTableEntry entry) => - _thisTree.addIfNotExists(key, entry); - - /// Determines if the item belongs to this collection. - /// - /// * item - _required_ - The object to locate in the collection. - /// The value can be a NULL reference (Nothing in Visual Basic). - /// - /// Returns true if item is found in the collection; otherwise False. - bool containsBase(_GenericTreeTableEntry item) { - if (item == null) { - return false; - } - - return _thisTree.containsBase(item); - } - - /// Copies the entire collection to a compatible one-dimensional array, - /// starting at the specified index of the target array. - /// - /// * array - _required_ - The one-dimensional array that is the destination - /// of the items copied from the ArrayList. The array must have zero-based - /// indexing. - /// * index - _required_ - The zero-based index in an array at which copying - /// begins. - void copyToBase(List array, int index) { - _thisTree.copyTo(array, index); - } - - /// Returns the zero-based index of the occurrence of the item - /// in the collection. - /// - /// * item - _required_ - The item to locate in the collection. The value - /// can be a NULL reference (Nothing in Visual Basic). - /// - /// Returns the zero-based index of the occurrence of the item within - /// the entire collection, if found; otherwise -1. - int indexOfBase(_GenericTreeTableEntry item) => _thisTree.indexOf(item); - - /// Used to find the key in the collection - /// - /// * key - _required_ - key needs to find - /// - /// Returns the value corresponding to the specified key - _GenericTreeTableEntry findKey(Object key) => _thisTree.findKey(key); - - /// Used to find the key approximate to the specified key - /// - /// * key - _required_ - key needs to be search - /// - /// Returns the value corresponds to the approximate key - _GenericTreeTableEntry findHighestSmallerOrEqualKey(Object key) => - _thisTree.findHighestSmallerOrEqualKey(key); - - /// Optimized access to a subsequent entry. - /// - /// * current - _required_ - current entry - /// - /// Returns next subsequent entry - _GenericTreeTableEntry getNextEntryBase( - _GenericTreeTableEntry current) => - _thisTree.getNextEntry(current); - - /// Optimized access to the previous entry. - /// - /// * current - _required_ - current entry - /// - /// Returns previous entry - _GenericTreeTableEntry getPreviousEntryBase( - _GenericTreeTableEntry current) => - _thisTree.getPreviousEntry(current); - - /// Used to find the index of the specified key - /// - /// * key - _required_ - key value - /// - /// Returns the index of the key - int indexOfKey(Object key) => _thisTree.indexOfKey(key); - - /// Inserts an item into the collection at the specified index. - /// - /// * index - _required_ - The zero-based index at which the item should be - /// inserted. - /// * item - _required_ - The item to insert. The value must not be a NULL - /// reference (Nothing in Visual Basic). - void insertBase(int index, _GenericTreeTableEntry item) { - _thisTree.insert(index, item); - } - - /// Removes the item at the specified index of the collection. - /// - /// * index - _required_ - The zero-based index of the item to remove. - void removeAtBase(int index) { - _thisTree.removeAt(index); - } - - /// Removes the specified item from the collection. - /// - /// * item - _required_ - The item to remove from the collection. - /// If the value is NULL or the item is not contained - /// in the collection, the method will do nothing. - /// - /// Returns the collection after removing the specified item from - /// the tree collection - bool removeBase(_GenericTreeTableEntry item) => _thisTree.remove(item); - - /// Adds the specified object to the collection. - /// - /// * value - _required_ - Value of the object to add. - /// - /// Returns the zero-based collection index at which the value has been added - @override - int add(Object value) => addBase(value); - - /// Add an item to the collection - /// - /// * item - _required_ - tree needs to be add in an collection - @override - void addGeneric(_GenericTreeTableEntry item) { - add(item); - } - - /// Optimizes insertion of many items when thisTree is initialized - /// for the first time. - @override - void beginInit() { - _thisTree.beginInit(); - } - - /// Removes all items from the collection. - @override - void clear() { - _thisTree.clear(); - } - - /// Copies the element from this collection into an array. - /// - /// * array - _required_ - The destination array. - /// * index - _required_ - The starting index in the destination array. - @override - void copyTo(List array, int index) { - copyToBase(array, index); - } - - /// Indicates whether the node belongs to this tree. - /// - /// * value - _required_ - value needs to be search in the tree - /// - /// Returns the boolean value indicating whether the node belongs - /// to this tree. - @override - bool contains(Object value) => containsBase(value); - - /// Ends optimization of insertion of items when thisTree is initialized - /// for the first time. - @override - void endInit() { - _thisTree.endInit(); - } - - /// Optimized access to a subsequent entry. - /// - /// * current - _required_ - current entry - /// - /// Returns next subsequent entry - @override - _TreeTableEntryBase getNextEntry(_TreeTableEntryBase current) => - getNextEntryBase(current); - - /// Optimized access to the previous entry. - /// - /// * current - _required_ - current entry - /// - /// Returns previous entry - @override - _TreeTableEntryBase getPreviousEntry(_TreeTableEntryBase current) => - getPreviousEntryBase(current); - - /// Inserts a node at the specified index. - /// - /// * index - _required_ - position where to insert the value - /// * value - _required_ - tree value need to be insert - @override - void insert(int index, Object value) { - insertBase(index, value); - } - - /// Gets the index of the specified object. - /// - /// * value - _required_ - Value of the object. - /// - /// Returns the index value of the object. - @override - int indexOf(Object value) => indexOfBase(value); - - /// Removes the node with the specified value. - /// - /// * value - _required_ - tree value needs to be remove - @override - void remove(Object value) { - removeBase(value); - } - - /// Gets the item at the zero-based index. - /// - /// * index - _required_ - index value - /// - /// Returns the Tree value - @override - Object operator [](int index) => _thisTree[index]; - - /// Sets the item at the zero-based index. - @override - void operator []=(int index, Object value) { - _thisTree[index] = value; - } - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/generic_tree_table_with_counter.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/generic_tree_table_with_counter.dart deleted file mode 100644 index d5b24c8d3..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/generic_tree_table_with_counter.dart +++ /dev/null @@ -1,535 +0,0 @@ -part of datagrid; - -/// A tree leaf with value and summary information. -/// -/// * V - _required_ - Represents a generic type counter source parameter. -/// * C - _required_ - Represents a generic type counter parameter. -class _GenericTreeTableWithCounterEntry extends _TreeTableWithCounterEntry { - /// Initializes a new instance of the GenericTreeTableWithCounterEntry class. - _GenericTreeTableWithCounterEntry(); - - /// Initializes a new instance of the GenericTreeTableWithCounterEntry class. - /// - /// * tree - _required_ - Tree instance - /// * value - _required_ - Tree value - _GenericTreeTableWithCounterEntry.fromGenericTreeTable( - _GenericTreeTableWithCounter tree, V value) { - super.value = value; - this.tree = tree.internalTree; - } - - /// Initializes a new instance of the GenericTreeTableWithCounterEntry class. - /// - /// * tree - _required_ - Tree instance - /// * value - _required_ - Tree value - _GenericTreeTableWithCounterEntry.fromTreeTable(_TreeTable tree, V value) { - super.value = value; - this.tree = tree; - } - - /// Gets the value attached to this leaf. - @override - V get value => super.value; - - /// Sets the value attached to this leaf. - @override - set value(Object value) { - super.value = value; - } - - /// Used to get the cumulative position of this node. - /// - /// Returns the cumulative position of this node. - C getCounterPositionBase() { - if (super.getCounterPosition is C) { - return super.getCounterPosition; - } else { - return null; - } - } - - /// Used to get the total of this node's counter and child nodes. - /// - /// Returns the total of this node's counter and child nodes. - C getCounterTotalBase() { - final Object _tableCounter = super.getCounterTotal; - if (_tableCounter is C) { - return _tableCounter; - } else { - return null; - } - } -} - -/// Tree table counter class -/// -/// * C - _required_ - Represents a generic type counter source parameter. -/// * V - _required_ - Represents a generic type counter parameter. -class _GenericTreeTableWithCounter - implements - _TreeTableBase, - _ListGenericBase<_GenericTreeTableWithCounterEntry>, - _ListBase { - /// Initializes a new instance of the `GenericTreeTableWithCounter{C,V}`class. - /// - /// * startPosition - _required_ - Tree instance - /// * sorted - _required_ - The boolean value holds whether the tree - /// is sorted or not - _GenericTreeTableWithCounter( - _TreeTableCounterBase startPosition, bool sorted) { - _thisTree = _TreeTableWithCounter(startPosition, sorted)..tag = this; - } - - _TreeTableWithCounter _thisTree; - - /// Gets the identifier - int get identifier => _identifier; - int _identifier = 0; - - /// Sets the identifier - set identifier(int value) { - if (value == identifier) { - return; - } - - _identifier = value; - } - - /// Gets the internal thisTree table. - _TreeTableWithCounter get internalTree => _thisTree; - - /// Gets the parent counter source - _TreeTableCounterSourceBase get parentCounterSource => - _thisTree.parentCounterSource; - - /// Sets the parent counter source - set parentCounterSource(_TreeTableCounterSourceBase value) { - _thisTree.parentCounterSource = value; - } - - /// Gets the tag that was associate with the tree - Object get tag => _thisTree.tag; - - /// Sets the tag that was associate with the tree - set tag(Object value) => _thisTree.tag = value; - - /// Gets an object that can be used to synchronize access - /// to the `ICollection`. - /// - /// Returns an object that can be used to synchronize - /// access to the `ICollection`. - Object get syncRootBase => null; - - /// Optimizes insertion of many items when thisTree is initialized - /// for the first time. - @override - void beginInit() { - _thisTree.beginInit(); - } - - /// Gets the comparer used by sorted trees. - @override - Comparable get comparer => _thisTree.comparer; - - /// Sets the comparer used by sorted trees. - @override - set comparer(Comparable value) { - _thisTree.comparer = value; - } - - /// Gets the number of items contained in the collection. - @override - int get count => _thisTree.getCount(); - - /// Ends optimization of insertion of items when thisTree is initialized - /// for the first time. - @override - void endInit() { - _thisTree.endInit(); - } - - /// Gets a value indicating whether the tree has summaries or not. - bool get hasSummaries => _thisTree.hasSummaries; - - /// Gets a value indicating whether the tree was initialize or not. - @override - bool get isInitializing => _thisTree.isInitializing; - - /// Gets a value indicating whether the collection has a fixed size. - /// - /// Returns `true` if the collection has a fixed size; otherwise, `false`. - @override - bool get isFixedSize => _thisTree.isFixedSize; - - /// Gets a value indicating whether the collection is read-only. - /// - /// Returns `true` if the collection is read-only; otherwise, `false`. - @override - bool get isReadOnly => _thisTree.isReadOnly; - - /// Gets a value indicating whether access to the `ICollection` - /// is synchronized (thread safe). - /// - /// Returns `True` if access to the `ICollection` is synchronized - /// (thread safe); otherwise, `false`. Returns `false`. - @override - bool get isSynchronized => false; - - /// Gets the root node. - @override - _TreeTableNodeBase get root => _thisTree.root; - - /// Gets a value indicating whether thisTree is sorted. - @override - bool get sorted => _thisTree.sorted; - - /// Gets an object that can be used to synchronize access to - /// the `ICollection`. - /// - /// Returns an object that can be used to synchronize access - /// to the `ICollection`. - @override - Object get syncRoot => syncRootBase; - - /// Add an item to the collection - /// - /// * item - _required_ - tree needs to be add in an collection - void addBase(_GenericTreeTableWithCounterEntry item) { - add(item); - } - - /// Add the key if the collection does not contains the specified key - /// - /// * key - _required_ - Key needs to be add in the collection - /// * entry - _required_ - The collection - /// - /// Returns the tree - _GenericTreeTableWithCounterEntry addIfNotExists( - Object key, _GenericTreeTableWithCounterEntry entry) => - _thisTree.addIfNotExists(key, entry); - - /// Used to find the key in the collection - /// - /// * key - _required_ - Key needs to find - /// - /// Returns the value corresponding to the specified key - _GenericTreeTableWithCounterEntry findKey(Object key) => - _thisTree.findKey(key); - - /// Used to find the key approximate to the specified key - /// - /// * key - _required_ - Key needs to be search - /// - /// Returns the value corresponds to the approximate key - _GenericTreeTableWithCounterEntry findHighestSmallerOrEqualKey( - Object key) => - _thisTree.findHighestSmallerOrEqualKey(key); - - /// Used to get the index of the key - /// - /// * key - _required_ - Key value - /// - /// Returns the index of the specified key - int indexOfKey(Object key) => _thisTree.indexOfKey(key); - - /// Marks all counters dirty. - /// - /// * notifyCounterSource - _required_ - boolean value - void invalidateCounterTopDown(bool notifyCounterSource) { - _thisTree.invalidateCounterTopDown(notifyCounterSource); - } - - /// Marks all summaries dirty. - /// - /// * notifySummariesSource - _required_ - if set to `true` - /// notify summaries source. - void invalidateSummariesTopDown(bool notifySummariesSource) { - _thisTree.invalidateSummariesTopDown(notifySummariesSource); - } - - /// Gets the total of all counters in this tree. - /// - /// Returns the total of all counters in this tree. - C getCounterTotal() => _thisTree.getCounterTotal(); - - /// The starting counter for this tree. - /// - /// Returns the starting counter for this tree. - C getStartCounterPosition() => _thisTree.getStartCounterPosition(); - - /// Overloaded.A cookie defines the type of counter. - /// - /// * searchPosition - _required_ - The search position. - /// * cookie - _required_ - The cookie. - /// - /// Returns an entry at the specified counter position. - _GenericTreeTableWithCounterEntry getEntryAtCounterPosition( - _TreeTableCounterBase searchPosition, int cookie) => - _thisTree.getEntryAtCounterPosition(searchPosition, cookie); - - /// Gets an entry at the specified counter position. - /// A cookie defines the type of counter. - /// - /// * searchPosition - _required_ - The search position. - /// * cookie - _required_ - The cookie. - /// * preferLeftMost - _required_ - Indicates if the leftmost entry should be - /// returned if multiple tree elements have the same SearchPosition. - /// - /// Returns an entry at the specified counter position. - _GenericTreeTableWithCounterEntry - getEntryAtCounterPositionWithThreeArgs( - Object searchPosition, int cookie, bool preferLeftMost) => - _thisTree.getEntryAtCounterPositionwithThreeParameter( - searchPosition, cookie, preferLeftMost); - - /// A cookie defines the type of counter. - /// - /// Gets the subsequent entry in the collection for which the specific - /// counter is not empty. - /// - /// * current - _required_ - The current. - /// * cookie - _required_ - The cookie. - /// - /// Returns the subsequent entry in the collection for which the specific - /// counter is not empty. - _GenericTreeTableWithCounterEntry getNextNotEmptyCounterEntry( - _GenericTreeTableWithCounterEntry current, int cookie) => - _thisTree.getNextNotEmptyCounterEntry(current, cookie); - - /// Gets the next entry in the collection for which CountVisible - /// counter is not empty. - /// - /// * current - _required_ - The current. - /// - /// Returns the next entry in the collection for which CountVisible - /// counter is not empty. - _GenericTreeTableWithCounterEntry getNextVisibleEntry( - _GenericTreeTableWithCounterEntry current) => - _thisTree.getNextVisibleEntry(current); - - /// Optimized access to a subsequent entry. - /// - /// * current - _required_ - current item - /// - /// Returns the next item in the collection. - _GenericTreeTableWithCounterEntry getNextEntryBase( - _GenericTreeTableWithCounterEntry current) => - _thisTree.getNextEntry(current); - - /// Gets the previous entry in the collection for which the specific counter - /// is not empty. - /// - /// * current - _required_ - current item - /// * cookie - _required_ - cookie value - /// - /// Returns the previous entry in the collection for which the specific - /// counter is not empty. - _GenericTreeTableWithCounterEntry getPreviousNotEmptyCounterEntry( - _GenericTreeTableWithCounterEntry current, int cookie) => - _thisTree.getPreviousNotEmptyCounterEntry(current, cookie); - - /// Gets the previous entry in the collection for which CountVisible - /// counter is not empty. - /// - /// * current - _required_ - The current. - /// - /// Returns the previous entry in the collection for which CountVisible - /// counter is not empty. - _GenericTreeTableWithCounterEntry getPreviousVisibleEntry( - _GenericTreeTableWithCounterEntry current) => - _thisTree.getPreviousVisibleEntry(current); - - /// Optimized access to the previous entry. - /// - /// * current - _required_ - Current item - /// - /// Returns the previous item in the collection. - _GenericTreeTableWithCounterEntry getPreviousEntryBase( - _GenericTreeTableWithCounterEntry current) => - _thisTree.getPreviousEntry(current); - - /// Gets an array of summary objects. - /// - /// * emptySummaries - _required_ - summary value - /// - /// Returns an array of summary objects. - List<_TreeTableSummaryBase> getSummaries( - _TreeTableEmptySummaryArraySourceBase emptySummaries) => - _thisTree.getSummaries(emptySummaries); - - /// Adds the specified object to the collection. - /// - /// * value - _required_ - Value of the object to add. - /// - /// Returns the zero-based collection index at which the value has been added - @override - int add(Object value) => addGeneric(value); - - /// Adds a value to the end of the collection. - /// - /// * item - _required_ - The item to be added to the end of the collection. - /// The value must not be a `NULL` reference (Nothing in Visual Basic). - /// - /// Returns the zero-based collection index at which the value has been added. - @override - int addGeneric(_GenericTreeTableWithCounterEntry item) => - _thisTree.addBase(item); - - /// Removes all items from the collection. - @override - void clear() { - _thisTree.clear(); - } - - /// Indicates whether the node belongs to this tree. - /// - /// * value - _required_ - value needs to be search in the tree - /// - /// Returns the boolean value indicating whether the node belongs - /// to this tree. - @override - bool contains(Object value) => containsGeneric(value); - - /// Determines if the item belongs to this collection. - /// - /// * item - _required_ - The object to locate in the collection. - /// The value can be a NULL reference (Nothing in Visual Basic). - /// - /// Returns true if item is found in the collection; otherwise False. - @override - bool containsGeneric(_GenericTreeTableWithCounterEntry item) { - if (item == null) { - return false; - } - - return _thisTree.contains(item); - } - - /// Copies the element from this collection into an array. - /// - /// * array - _required_ - The destination array. - /// * index - _required_ - The starting index in the destination array. - @override - void copyTo(List array, int index) { - copyToGeneric(array, index); - } - - /// Copies the entire collection to a compatible one-dimensional array, - /// starting at the specified index of the target array. - /// - /// * array - _required_ - The one-dimensional array that is the destination - /// of the items copied from the ArrayList. The array must have zero-based - /// indexing. - /// * index - _required_ - The zero-based index in an array at which c - /// opying begins. - @override - void copyToGeneric( - List<_GenericTreeTableWithCounterEntry> array, int index) { - _thisTree.copyTo(array, index); - } - - /// Optimized access to a subsequent entry. - /// - /// * current - _required_ - Current entry - /// - /// Returns next subsequent entry - @override - _TreeTableEntryBase getNextEntry(_TreeTableEntryBase current) => - getNextEntryBase(current); - - /// Optimized access to the previous entry. - /// - /// * current - _required_ - Current entry - /// - /// Returns previous entry - @override - _TreeTableEntryBase getPreviousEntry(_TreeTableEntryBase current) => - getPreviousEntryBase(current); - - /// Returns the index of the specified object. - /// - /// * value - _required_ - Value of the object. - /// - /// Returns index value of the object. - @override - int indexOf(Object value) => indexOfGeneric(value); - - /// Inserts an item into the collection at the specified index. - /// - /// * index - _required_ - The zero-based index at which the item should be - /// inserted. - /// * item - _required_ - The item to insert. The value must not be a `NULL` - /// reference (Nothing in Visual Basic). - @override - void insertGeneric(int index, _GenericTreeTableWithCounterEntry item) { - _thisTree.insert(index, item); - } - - /// Inserts a node at the specified index. - /// - /// * index - _required_ - position where to insert the value - /// * value - _required_ - tree value need to be insert - @override - void insert(int index, Object value) { - insertGeneric(index, value); - } - - /// Returns the zero-based index of the occurrence of the item in the - /// collection. - /// - /// * item - _required_ - The item to locate in the collection. - /// The value can be a `NULL` reference (Nothing in Visual Basic). - /// - /// Returns the zero-based index of the occurrence of the item within - /// the entire collection, if found; otherwise `-1`. - @override - int indexOfGeneric(_GenericTreeTableWithCounterEntry item) => - _thisTree.indexOf(item); - - /// Removes the specified item from the collection. - /// - /// * item - _required_ - The item to remove from the collection. - /// If the value is `NULL` or the item is not contained - /// in the collection, the method will do nothing. - /// - /// Returns the collection after removing the specified item from - /// the tree collection - @override - bool removeGeneric(_GenericTreeTableWithCounterEntry item) => - _thisTree.remove(item); - - /// Removes the item at the specified index of the collection. - /// - /// * index - _required_ - The zero-based index of the item to remove. - @override - void removeAt(int index) { - _thisTree.removeAt(index); - } - - /// Removes the node with the specified value. - /// - /// * value - _required_ - tree value needs to be remove - @override - void remove(Object value) { - removeGeneric(value); - } - - /// Gets the item at the zero-based index. - /// - /// * index - _required_ - Index value - /// - /// Returns this tree instance. - @override - Object operator [](int index) => _thisTree[index]; - - /// Sets the item at the zero-based index. - @override - void operator []=(int index, Object value) { - _thisTree[index] = value; - } - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/generic_tree_table_with_summary.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/generic_tree_table_with_summary.dart deleted file mode 100644 index 2f37aa51e..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/generic_tree_table_with_summary.dart +++ /dev/null @@ -1,416 +0,0 @@ -part of datagrid; - -/// A tree leaf with value and summary information. -/// -/// * T - _required_ - Represents a generic type parameter -class _GenericTreeTableWithSummaryEntryBase - extends _TreeTableWithSummaryEntryBase { - /// Initializes a new instance of the _GenericTreeTableWithSummaryEntryBase - /// class. - _GenericTreeTableWithSummaryEntryBase(); - - /// Initializes a new instance of the _GenericTreeTableWithSummaryEntryBase - /// class. - /// - /// * tree - _required_ - Tree instance - /// * value - _required_ -Ttree value - _GenericTreeTableWithSummaryEntryBase.genericTreeTable( - _GenericTreeTableWithSummary tree, T value) { - super.value = value; - this.tree = tree.internalTree; - } - - /// Initializes a new instance of the _GenericTreeTableWithSummaryEntryBase - /// class. - /// - /// * tree - _required_ - Tree instance - /// * value - _required_ - Tree value - _GenericTreeTableWithSummaryEntryBase.treeTable(_TreeTable tree, T value) { - super.value = value; - this.tree = tree; - } - - List list; - - /// Gets the value attached to this leaf. - @override - T get value => super.value; - - /// Sets the value attached to this leaf. - @override - set value(Object value) { - super.value = value; - } -} - -///Tree table summary class -/// -/// * T - _required_ - Represents a generic type parameter -class _GenericTreeTableWithSummary extends _TreeTableBase { - //with IListGeneric<_GenericTreeTableWithSummaryEntryBase> - - ///Initializes a new instance of the [GenericTreeTableWithSummary]` class. - /// - /// * sorted - _required_ - boolean value - _GenericTreeTableWithSummary(bool sorted) { - _thisTree = _TreeTableWithSummary(sorted)..tag = this; - } - - _TreeTableWithSummary _thisTree; - - // public GenericBinaryTreeWithSummaryCollection - // BinaryTreeCollection { get; set; } - - /// Gets an identifier. - int get identifier => _identifier; - int _identifier = 0; - - /// Sets an identifier. - set identifier(int value) { - if (value == _identifier) { - return; - } - - _identifier = value; - } - - /// Gets the internal thisTree table. - _TreeTableWithSummary get internalTree => _thisTree; - - /// Gets the tag that was associate with the tree - Object get tag => _thisTree.tag; - - /// Sets the tag that was associate with the tree - set tag(Object value) => _thisTree.tag = value; - - /// Gets an object that can be used to synchronize access - /// to the `ICollection`. - /// - /// Returns an object that can be used to synchronize access - /// to the `ICollection`. - Object get syncRootBase => null; - - /// Gets a value indicating whether the tree has summaries or not. - bool get hasSummaries => _thisTree.hasSummaries; - - /// Gets the comparer used by sorted trees. - @override - Comparable get comparer => _thisTree.comparer; - - /// Sets the comparer used by sorted trees. - @override - set comparer(Comparable value) { - _thisTree.comparer; - } - - /// Optimizes insertion of many items when thisTree is initialized - /// for the first time. - @override - void beginInit() { - _thisTree.beginInit(); - } - - /// Gets the number of items contained in the collection. - @override - int get count => _thisTree.getCount(); - - /// Gets a value indicating whether the collection has a fixed size. - /// - /// Returns `true` if the collection has a fixed size; otherwise, `false`. - @override - bool get isFixedSize => _thisTree.isFixedSize; - - /// Gets a value indicating whether the tree was initialize or not. - @override - bool get isInitializing => _thisTree.isInitializing; - - /// Gets a value indicating whether the collection is read-only. - /// - /// Returns true if the collection is read-only. otherwise, false. - @override - bool get isReadOnly => _thisTree.isReadOnly; - - /// Gets a value indicating whether access to the `ICollection` - /// is synchronized (thread safe). - /// - /// Returns `True` if access to the `ICollection` is synchronized - /// (thread safe). otherwise, `false`. Returns `false`. - @override - bool get isSynchronized => false; - - /// Gets the root node. - @override - _TreeTableNodeBase get root => _thisTree.root; - - /// Gets a value indicating whether thisTree is sorted. - @override - bool get sorted => _thisTree.sorted; - - /// Gets an object that can be used to synchronize access - /// to the `ICollection`. - /// - /// An object that can be used to synchronize access to the `ICollection`. - @override - Object get syncRoot => syncRootBase; - - /// Add the key if the collection does not contains the specified key - /// - /// * key - _required_ - Key needs to be add in the collection - /// * entry - _required_ - The collection - /// - /// Returns the tree - _GenericTreeTableWithSummaryEntryBase addIfNotExists( - Object key, _GenericTreeTableWithSummaryEntryBase entry) => - _thisTree.addIfNotExists(key, entry); - - /// Used to find the index of the specified key - /// - /// * key - _required_ - Key value - /// - /// Returns the index of the key - int indexOfKey(Object key) => _thisTree.indexOfKey(key); - - /// Used to find the key in a TreeTable - /// - /// * key - _required_ - Key needs to find - /// - /// Returns the instance for TreeTable - _GenericTreeTableWithSummaryEntryBase findKey(Object key) => - _thisTree.findKey(key); - - /// Used to find the key approximate to the specified key - /// - /// * key - _required_ - Key needs to be search - /// - /// Returns the value corresponds to the approximate key - _GenericTreeTableWithSummaryEntryBase findHighestSmallerOrEqualKey( - Object key) { - if (_thisTree.findHighestSmallerOrEqualKey(key) - is _GenericTreeTableWithSummaryEntryBase) { - return _thisTree.findHighestSmallerOrEqualKey(key); - } else { - return null; - } - } - - /// Marks all summaries dirty. - /// - /// * notifySummariesSource - _required_ - If set to `true` - /// notify summaries source. - void invalidateSummariesTopDown(bool notifySummariesSource) { - _thisTree.invalidateSummariesTopDown(notifySummariesSource); - } - - /// Optimized access to a subsequent entry. - /// - /// * current - _required_ - current value - /// - /// Returns next subsequent entry - _GenericTreeTableWithSummaryEntryBase getNextEntryBase( - _GenericTreeTableWithSummaryEntryBase current) => - _thisTree.getNextEntry(current); - - /// Optimized access to the previous entry. - /// - /// * current - _required_ - Current item - /// - /// Returns previous entry - _GenericTreeTableWithSummaryEntryBase getPreviousEntryBase( - _GenericTreeTableWithSummaryEntryBase current) => - _thisTree.getPreviousEntry(current); - - /// Gets an array of summary objects. - /// - /// * emptySummaries - _required_ - Summary value - /// - /// Returns an array of summary objects. - List<_TreeTableSummaryBase> getSummaries( - _TreeTableEmptySummaryArraySourceBase emptySummaries) => - _thisTree.getSummaries(emptySummaries); - - /// Removes the specified item from the collection. - /// - /// * item - _required_ - The item to remove from the collection. - /// If the value is NULL or the item is not contained - /// in the collection, the method will do nothing. - /// - /// Returns the removed item - bool removeBase(_GenericTreeTableWithSummaryEntryBase item) => - _thisTree.remove(item); - - /// Ends optimization of insertion of items when thisTree is initialized - /// for the first time. - @override - void endInit() { - _thisTree.endInit(); - } - - /// Optimized access to a subsequent entry. - /// - /// * current - _required_ - current entry - /// - /// Returns next subsequent entry - @override - _TreeTableEntryBase getNextEntry(_TreeTableEntryBase current) => - getNextEntryBase(current); - - /// Optimized access to the previous entry. - /// - /// * current - _required_ - Current entry - /// - /// Returns previous entry - @override - _TreeTableEntryBase getPreviousEntry(_TreeTableEntryBase current) => - getPreviousEntryBase(current); - - /// Adds a value to the end of the collection. - /// - /// * item - _required_ - The item to be added to the end of the collection. - /// The value must not be a NULL reference (Nothing in Visual Basic). - /// - /// Returns the zero-based collection index at which the value has been added. - int addBase(_GenericTreeTableWithSummaryEntryBase item) => - _thisTree.add(item); - - /// Add an item to the collection - /// - /// * item - _required_ - tree needs to be add in an collection - void addGeneric(_GenericTreeTableWithSummaryEntryBase item) { - add(item); - } - - /// Removes all items from the collection. - void clearBase() { - _thisTree.clear(); - } - - /// Determines if the item belongs to this collection. - /// - /// * item - _required_ - The object to locate in the collection. - /// The value can be a `NULL` reference (Nothing in Visual Basic). - /// - /// Returns true if item is found in the collection; otherwise False. - bool containsBase(_GenericTreeTableWithSummaryEntryBase item) { - if (item == null) { - return false; - } - - return _thisTree.contains(item); - } - - /// Copies the entire collection to a compatible one-dimensional array, - /// starting at the specified index of the target array. - /// - /// * array - _required_ - The one-dimensional array that is the destination - /// of the items copied from the ArrayList. The array must have zero-based - /// indexing. - /// * index - _required_ - The zero-based index in an array at which - /// copying begins. - void copyToBase(List array, int index) { - _thisTree.copyTo(array, index); - } - - /// Returns the zero-based index of the occurrence of the item in the - /// collection. - /// - /// * item - _required_ - The item to locate in the collection. The value - /// can be a `NULL` reference (Nothing in Visual Basic). - /// - /// Returns the zero-based index of the occurrence of the item within - /// the entire collection, if found; otherwise `-1`. - int indexOfBase(_GenericTreeTableWithSummaryEntryBase item) => - _thisTree.indexOf(item); - - /// Adds the specified object to the collection. - /// - /// * value - _required_ - Value of the object to add. - /// - /// Returns the zero-based collection index at which the value has been added - @override - int add(Object value) { - if (value is _GenericTreeTableWithSummaryEntryBase) { - return addBase(value); - } else { - return -1; - } - } - - /// Indicates whether the node belongs to this tree. - /// - /// * value - _required_ - Value needs to be search in the tree - /// - /// Returns the boolean value indicating whether the node belongs - /// to this tree. - @override - bool contains(Object value) { - if (value is _GenericTreeTableWithSummaryEntryBase) { - return containsBase(value); - } else { - return false; - } - } - - /// Copies the element from this collection into an array. - /// - /// * array - _required_ - The destination array. - /// * index - _required_ - The starting index in the destination array. - @override - void copyTo(List array, int index) { - copyToBase(array, index); - } - - /// Inserts a node at the specified index. - /// - /// * index - _required_ - Position where to insert the value - /// * value - _required_ - Tree value need to be insert - @override - void insert(int index, Object value) { - if (value is _GenericTreeTableWithSummaryEntryBase) { - insert(index, value); - } - } - - /// Returns the index of the specified object. - /// - /// * value - _required_ - Value of the object. - /// - /// Returns index value of the object. - @override - int indexOf(Object value) { - if (value is _GenericTreeTableWithSummaryEntryBase) { - return indexOfBase(value); - } else { - return -1; - } - } - - /// Removes the node with the specified value. - /// - /// * value - _required_ - Tree value needs to be remove - @override - void remove(Object value) { - if (value is _GenericTreeTableWithSummaryEntryBase) { - removeBase(value); - } - } - - /// Gets the item at the zero-based index. - /// - /// * index - _required_ - Index value - /// - /// Returns the Tree value - @override - _GenericTreeTableWithSummaryEntryBase operator [](int index) { - if (_thisTree[index] is _GenericTreeTableWithSummaryEntryBase) { - return _thisTree[index]; - } else { - return null; - } - } - - /// Sets the item at the zero-based index. - @override - void operator []=(int index, Object value) { - _thisTree[index] = value; - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table.dart index ec182ad45..b738ab26f 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table.dart @@ -1962,20 +1962,6 @@ class _TreeTableEntrySourceCollection extends _ListBase { inner = _TreeTable(false); } - ///Initializes a new instance of the `TreeTableEntrySourceCollection` class. - /// - /// * inner - _required_ - Tree table - _TreeTableEntrySourceCollection.fromInner(_TreeTableBase inner) { - inner = inner; - } - - ///Initializes a new instance of the `TreeTableEntrySourceCollection` class. - /// - /// * sorted - _required_ - Boolean value - _TreeTableEntrySourceCollection.fromSorted(bool sorted) { - inner = _TreeTable(sorted); - } - _TreeTableBase inner; /// Gets the number of objects in this collection. diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_generic_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_generic_base.dart index 9b16d2f9e..ee9f55cda 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_generic_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_generic_base.dart @@ -1,78 +1,5 @@ part of datagrid; -/// Generic List -/// -/// An indexable collection of objects with a length. -/// Creates a list of the given length. -/// The created list is fixed-length if [length] is provided. -class _ListGenericBase - implements _CollectionGenericBase, _EnumerableGenericBase { - /// Find an element used by index - /// - /// * item - _required_ - Index Position - int indexOfGeneric(T item); - - /// Insert an element to the list used by index. - /// - /// * index - _required_ - Index position - /// * item - _required_ - Item - void insertGeneric(int index, T item) {} - - /// Remove an element used by index - /// - /// * index - _required_ - Index value - void removeAtGeneric(int index) {} - - /// Gets the index value. - /// - /// Returns the index value. - Object operator [](int index) => this[index]; - - /// Sets the index value. - void operator []=(int index, Object value) => this[index] = value; - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} - -/// Generic collection -class _CollectionGenericBase implements _EnumerableGenericBase { - /// Gets the count. - int get countGeneric => _count; - int _count; - - /// Gets the read only value. - bool get isReadOnlyGeneric => _isReadOnly; - bool _isReadOnly; - - /// Add an item to the list - /// - /// * item - _required_ - An element - void addGeneric(T item); - - /// Clear the entire collection. - void clearGeneric(); - - /// Check whether the value is found or not. - /// - /// * item - _required_ - An element - bool containsGeneric(T item); - - /// Copy an element based on index. - /// - /// * list - _required_ - List of element - /// * index - _required_ - arrayIndex position - void copyToGeneric(List array, int arrayIndex); - - /// Remove the element based on index. - /// - /// * item - The index position. - bool removeGeneric(T item); - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} - /// Generic Enumerable abstract class _EnumerableGenericBase with IterableMixin { /// Gets the enumerator diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_subset.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_subset.dart index 30f144545..d9dc3d137 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_subset.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_subset.dart @@ -11,7 +11,7 @@ part of datagrid; // PixelScrollAxis's SetFooterLineCount method. // ignore: unused_element class _DistanceCounterSubset extends _DistanceCounterCollectionBase - with _DistanceCounterCollectionBase { + with _DistancesHostBase { /// Initializes a new instance of the DistanceCounterSubset class. /// /// * trackedParentCollection - _required_ - The parent collection for which @@ -26,6 +26,7 @@ class _DistanceCounterSubset extends _DistanceCounterCollectionBase int start = 0; /// Gets an object that implements the `Distances` property. + @override _DistanceCounterCollectionBase get distances => _distances; /// Gets an distance the `Distances` property. diff --git a/packages/syncfusion_flutter_datagrid/lib/src/sfdatagrid.dart b/packages/syncfusion_flutter_datagrid/lib/src/sfdatagrid.dart index 663947a2a..4276210c1 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/sfdatagrid.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/sfdatagrid.dart @@ -53,6 +53,13 @@ typedef DataGridCellDoubleTapCallback = void Function( 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); + /// A material design datagrid. /// /// DataGrid lets you display and manipulate data in a tabular view. It is built @@ -118,24 +125,24 @@ typedef DataGridCellLongPressCallback = void Function( /// final int salary; /// } /// -/// class EmployeeDataSource extends DataGridSource{ +/// class EmployeeDataSource extends DataGridSource { /// @override -/// List get dataSource => _employees; +/// List get dataSource => _employees; /// /// @override -/// getCellValue(int rowIndex, String columnName){ +/// getValue(Employee employee, String columnName){ /// switch (columnName) { /// case 'id': -/// return _employees[rowIndex].id; +/// return employee.id; /// break; /// case 'name': -/// return _employees[rowIndex].name; +/// return employee.name; /// break; /// case 'salary': -/// return _employees[rowIndex].salary; +/// return employee.salary; /// break; /// case 'designation': -/// return _employees[rowIndex].designation; +/// return employee.designation; /// break; /// default: /// return ‘ ’; @@ -156,6 +163,7 @@ class SfDataGrid extends StatefulWidget { double headerRowHeight, double defaultColumnWidth, GridLinesVisibility gridLinesVisibility, + GridLinesVisibility headerGridLinesVisibility, ColumnWidthCalculationMode columnWidthCalculationMode, ColumnWidthCalculationRange columnWidthCalculationRange, ColumnWidthMode columnWidthMode, @@ -170,6 +178,7 @@ class SfDataGrid extends StatefulWidget { bool allowTriStateSorting, bool showSortNumbers, SortingGestureType sortingGestureType, + List stackedHeaderRows, this.selectionManager, this.controller, this.columnSizer, @@ -186,7 +195,11 @@ class SfDataGrid extends StatefulWidget { this.onCellTap, this.onCellDoubleTap, this.onCellSecondaryTap, - this.onCellLongPress}) + this.onCellLongPress, + bool isScrollbarAlwaysShown, + ScrollPhysics horizontalScrollPhysics, + ScrollPhysics verticalScrollPhysics, + this.loadMoreViewBuilder}) : assert(source != null), assert(columns != null), assert(frozenColumnsCount != null && frozenColumnsCount >= 0), @@ -196,9 +209,11 @@ class SfDataGrid extends StatefulWidget { assert(footerFrozenRowsCount != null && footerFrozenRowsCount >= 0), rowHeight = rowHeight, headerRowHeight = headerRowHeight, - defaultColumnWidth = defaultColumnWidth ?? (kIsWeb ? 100.0 : 90.0), + defaultColumnWidth = defaultColumnWidth, gridLinesVisibility = gridLinesVisibility ?? GridLinesVisibility.horizontal, + headerGridLinesVisibility = + headerGridLinesVisibility ?? GridLinesVisibility.horizontal, columnWidthCalculationMode = columnWidthCalculationMode ?? ColumnWidthCalculationMode.textSize, columnWidthMode = columnWidthMode ?? ColumnWidthMode.none, @@ -211,6 +226,12 @@ class SfDataGrid extends StatefulWidget { allowTriStateSorting = allowTriStateSorting ?? false, showSortNumbers = showSortNumbers ?? false, sortingGestureType = sortingGestureType ?? SortingGestureType.tap, + stackedHeaderRows = stackedHeaderRows ?? const [], + isScrollbarAlwaysShown = isScrollbarAlwaysShown ?? false, + horizontalScrollPhysics = + horizontalScrollPhysics ?? const AlwaysScrollableScrollPhysics(), + verticalScrollPhysics = + verticalScrollPhysics ?? const AlwaysScrollableScrollPhysics(), super(key: key); /// The height of each row except the column header. @@ -295,6 +316,19 @@ class SfDataGrid extends StatefulWidget { /// Also refer [GridLinesVisibility] final GridLinesVisibility gridLinesVisibility; + /// How the border should be visible in header cells. + /// + /// Decides whether vertical or horizontal or both the borders or no borders should be drawn. + /// + /// [GridLinesVisibility.horizontal] will be useful if you are using [stackedHeaderRows] to improve the readability. + /// + /// Defaults to [GridLinesVisibility.horizontal] + /// + /// Also refer [GridLinesVisibility]. + /// + /// See also, [gridLinesVisibility] – To set the border for cells other than header cells. + final GridLinesVisibility headerGridLinesVisibility; + /// Invoked when the style for each cell is applied. /// /// Users can set the styling for the cells based on the condition. @@ -608,6 +642,187 @@ class SfDataGrid extends StatefulWidget { /// [allowSorting] final SortingGestureType sortingGestureType; + /// The collection of [StackedHeaderRow]. + /// + /// Stacked headers enable you to display headers that span across multiple + /// columns and rows. These rows are displayed above to the regular column + /// headers. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfDataGrid(source: _employeeDataSource, columns: [ + /// GridNumericColumn(mappingName: 'orderID', headerText: 'ID'), + /// GridDateTimeColumn( + /// mappingName: 'orderDate', headerText: 'Order Date'), + /// GridTextColumn( + /// mappingName: 'designation', headerText: 'Designation'), + /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary') + /// ], stackedHeaderRows: [ + /// StackedHeaderRow(stackedColumns: [ + /// StackedColumn( + /// childColumns: " orderID, orderDate", headerText: "Order Details"), + /// ]) + /// ]); + /// } + /// ``` + final List stackedHeaderRows; + + /// Indicates whether the horizontal and vertical scrollbars should always + /// be visible. When false, both the scrollbar will be shown during scrolling + /// and will fade out otherwise. When true, both the scrollbar will always be + /// visible and never fade out. + /// + /// Defaults to false + final bool isScrollbarAlwaysShown; + + /// How the horizontal scroll view should respond to user input. + /// For example, determines how the horizontal scroll view continues to animate after the user stops dragging the scroll view. + /// + /// Defaults to AlwaysScrollableScrollPhysics() + final ScrollPhysics horizontalScrollPhysics; + + /// How the vertical scroll view should respond to user input. + /// For example, determines how the vertical scroll view continues to animate after the user stops dragging the scroll view. + /// + /// Defaults to AlwaysScrollableScrollPhysics() + final ScrollPhysics verticalScrollPhysics; + + /// A builder that sets the widget to display at the bottom of the datagrid + /// when vertical scrolling reaches the end of the datagrid. + /// + /// You should override [DataGridSource.handleLoadMoreRows] method to load + /// more rows and then notify the datagrid about the changes. The + /// [DataGridSource.handleLoadMoreRows] can be called to load more rows from + /// this builder using [loadMoreRows] function which is passed as a parameter + /// to this builder. + /// + /// ## Infinite scrolling + /// + /// The example below demonstrates infinite scrolling by showing the circular + /// progress indicator until the rows are loaded when vertical scrolling + /// reaches the end of the datagrid, + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Syncfusion Flutter DataGrid')), + /// body: SfDataGrid( + /// source: employeeDataSource, + /// loadMoreViewBuilder: + /// (BuildContext context, LoadMoreRows loadMoreRows) { + /// Future loadRows() async { + /// await loadMoreRows(); + /// return Future.value('Completed'); + /// } + /// + /// return FutureBuilder( + /// initialData: 'loading', + /// future: loadRows(), + /// builder: (context, snapShot) { + /// if (snapShot.data == 'loading') { + /// return Container( + /// height: 98.0, + /// color: Colors.white, + /// width: double.infinity, + /// alignment: Alignment.center, + /// child: CircularProgressIndicator(valueColor: + /// AlwaysStoppedAnimation(Colors.deepPurple))); + /// } else { + /// return SizedBox.fromSize(size: Size.zero); + /// } + /// }, + /// ); + /// }, + /// columns: [ + /// GridNumericColumn(mappingName: 'id', headerText: 'ID'), + /// GridTextColumn(mappingName: 'name', headerText: 'Name'), + /// GridTextColumn( + /// mappingName: 'designation', headerText: 'Designation'), + /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary'), + /// ], + /// ), + /// ); + /// } + /// ``` + /// + /// ## Load more button + /// + /// The example below demonstrates how to show the button when vertical + /// scrolling is reached at the end of the datagrid and display the circular + /// indicator when you tap that button. In the onPressed flatbutton callback, + /// you can call the [loadMoreRows] function to add more rows. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Syncfusion Flutter DataGrid')), + /// body: SfDataGrid( + /// source: employeeDataSource, + /// loadMoreViewBuilder: + /// (BuildContext context, LoadMoreRows loadMoreRows) { + /// bool showIndicator = false; + /// return StatefulBuilder( + /// builder: (BuildContext context, StateSetter setState) { + /// if (showIndicator) { + /// return Container( + /// height: 98.0, + /// color: Colors.white, + /// width: double.infinity, + /// alignment: Alignment.center, + /// child: CircularProgressIndicator(valueColor: + /// AlwaysStoppedAnimation(Colors.deepPurple))); + /// } else { + /// return Container( + /// height: 98.0, + /// color: Colors.white, + /// width: double.infinity, + /// alignment: Alignment.center, + /// child: Container( + /// height: 36.0, + /// width: 142.0, + /// child: FlatButton( + /// color: Colors.deepPurple, + /// child: Text('LOAD MORE', + /// style: TextStyle(color: Colors.white)), + /// onPressed: () async { + /// if (context is StatefulElement && + /// context.state != null && + /// context.state.mounted) { + /// setState(() { + /// showIndicator = true; + /// }); + /// } + /// await loadMoreRows(); + /// if (context is StatefulElement && + /// context.state != null && + /// context.state.mounted) { + /// setState(() { + /// showIndicator = false; + /// }); + /// } + /// }, + /// ), + /// ), + /// ); + /// } + /// }); + /// }, + /// columns: [ + /// GridNumericColumn(mappingName: 'id', headerText: 'ID'), + /// GridTextColumn(mappingName: 'name', headerText: 'Name'), + /// GridTextColumn( + /// mappingName: 'designation', headerText: 'Designation'), + /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary'), + /// ], + /// ), + /// ); + /// } + /// ``` + final LoadMoreViewBuilder loadMoreViewBuilder; + @override State createState() => _SfDataGridState(); } @@ -679,7 +894,7 @@ class _SfDataGridState extends State { _dataGridSettings.controller = widget.controller ?? DataGridController() .._dataGridStateDetails = _dataGridStateDetails; - _controller?.addListener(_handleDataGridSourceChanged); + _controller?.addListener(_handleDataSourceChangeListeners); //AutoFit controller initializing _columnSizer = widget.columnSizer ?? ColumnSizer() .._dataGridStateDetails = _dataGridStateDetails; @@ -722,11 +937,21 @@ class _SfDataGridState extends State { .._isGridLoaded = true; } + void _updateVisualDensity() { + final baseDensity = _dataGridSettings.visualDensity.baseSizeAdjustment; + + _dataGridSettings.headerRowHeight = widget.headerRowHeight ?? + _dataGridSettings.headerRowHeight + baseDensity.dy; + + _dataGridSettings.rowHeight = + widget.rowHeight ?? _dataGridSettings.rowHeight + baseDensity.dy; + } + void _initializeDataGridDataSource() { if (_source != widget.source) { - _source?.removeListener(_handleDataGridSourceChanged); + _removeDataGridSourceListeners(); _source = widget.source; - _source?.addListener(_handleDataGridSourceChanged); + _addDataGridSourceListeners(); } _source._updateDataSource(); } @@ -755,6 +980,8 @@ class _SfDataGridState extends State { _cellRenderers['Widget'] = GridCellWidgetRenderer(_dataGridStateDetails); _cellRenderers['DateTime'] = GridCellDateTimeRenderer(_dataGridStateDetails); + _cellRenderers['StackedHeader'] = + _GridStackedHeaderCellRenderer(_dataGridStateDetails); if (widget.onCellRenderersCreated != null) { widget.onCellRenderersCreated(_cellRenderers); @@ -814,15 +1041,14 @@ class _SfDataGridState extends State { widget.source._effectiveDataSource.length, -1); } - if (!_suspendDataPagerUpdate) { - widget.source.notifyListeners(); - } - _container .._updateRowAndColumnCount() .._refreshView() .._isDirty = true; }); + if (_dataGridSettings.source.shouldRecalculateColumnWidths()) { + _dataGridSettings.columnSizer._resetAutoCalculation(); + } } void _processSorting() { @@ -853,7 +1079,11 @@ class _SfDataGridState extends State { _container._needToRefreshColumn = true; } - void _handleDataGridSourceChanged( + void _handleNotifyListeners() { + _processUpdateDataSource(); + } + + void _handleDataSourceChangeListeners( {RowColumnIndex rowColumnIndex, String propertyName, bool recalculateRowHeight}) { @@ -890,6 +1120,13 @@ class _SfDataGridState extends State { if (rowColumnIndex == null && propertyName == 'Sorting') { _processSorting(); } + + if (propertyName == 'hoverOnHeaderCell') { + setState(() { + // To rebuild the datagrid on hovering the header cell. isDirtly already + // been set in header cell widget itself + }); + } } void _updateDataGridStateDetails() { @@ -902,9 +1139,11 @@ class _SfDataGridState extends State { widget.rowHeight ?? (_dataGridSettings.rowHeight ?? _rowHeight) ..headerRowHeight = widget.headerRowHeight ?? (_dataGridSettings.headerRowHeight ?? _headerRowHeight) + ..visualDensity = _dataGridSettings.visualDensity + ..defaultColumnWidth = + widget.defaultColumnWidth ?? _dataGridSettings.defaultColumnWidth ..source = _source ..columns = _columns - ..defaultColumnWidth = widget.defaultColumnWidth ..headerLineCount = _container._headerLineCount ..onQueryCellStyle = widget.onQueryCellStyle ..onQueryRowStyle = widget.onQueryRowStyle @@ -913,6 +1152,7 @@ class _SfDataGridState extends State { ..onQueryRowHeight = widget.onQueryRowHeight ..dataGridThemeData = _dataGridThemeData ..gridLinesVisibility = widget.gridLinesVisibility + ..headerGridLinesVisibility = widget.headerGridLinesVisibility ..columnWidthMode = widget.columnWidthMode ..columnSizer = _columnSizer ..columnWidthCalculationMode = widget.columnWidthCalculationMode @@ -936,7 +1176,12 @@ class _SfDataGridState extends State { ..allowTriStateSorting = widget.allowTriStateSorting ..sortingGestureType = widget.sortingGestureType ..showSortNumbers = widget.showSortNumbers - ..isControlKeyPressed = false; + ..isControlKeyPressed = false + ..stackedHeaderRows = widget.stackedHeaderRows + ..isScrollbarAlwaysShown = widget.isScrollbarAlwaysShown + ..horizontalScrollPhysics = widget.horizontalScrollPhysics + ..verticalScrollPhysics = widget.verticalScrollPhysics + ..loadMoreViewBuilder = widget.loadMoreViewBuilder; } _DataGridSettings _onDataGridStateDetailsChanged() => _dataGridSettings; @@ -968,6 +1213,8 @@ class _SfDataGridState extends State { oldWidget.allowMultiColumnSorting != widget.allowMultiColumnSorting; final isShowSortNumbersChanged = oldWidget.showSortNumbers != widget.showSortNumbers; + final isStackedHeaderRowsChanged = !listEquals( + oldWidget.stackedHeaderRows, widget.stackedHeaderRows); if (isSourceChanged || isColumnsChanged || isDataSourceChanged || @@ -979,6 +1226,7 @@ class _SfDataGridState extends State { isSortingChanged || isMultiColumnSortingChanged || isShowSortNumbersChanged || + isStackedHeaderRowsChanged || oldWidget.rowHeight != widget.rowHeight || oldWidget.headerRowHeight != widget.headerRowHeight || oldWidget.defaultColumnWidth != widget.defaultColumnWidth || @@ -999,16 +1247,21 @@ class _SfDataGridState extends State { } if (isDataGridControllerChanged) { - oldWidget.controller?.removeListener(_handleDataGridSourceChanged); + oldWidget.controller?.removeListener(_handleDataSourceChangeListeners); _controller = _dataGridSettings.controller = widget.controller ?? _controller .._dataGridStateDetails = _dataGridStateDetails; - _controller?.addListener(_handleDataGridSourceChanged); + _controller?.addListener(_handleDataSourceChangeListeners); } _initializeProperties(); + + if (isStackedHeaderRowsChanged) { + _onStackedHeaderRowsPropertyChanged(oldWidget, widget); + } + _container._refreshDefaultLineSize(); _updateSelectionController( @@ -1025,6 +1278,7 @@ class _SfDataGridState extends State { isColumnSizerChanged || isFrozenColumnPaneChanged || isSortingChanged || + isStackedHeaderRowsChanged || widget.allowSorting && isMultiColumnSortingChanged || widget.allowSorting && widget.allowMultiColumnSorting && @@ -1035,7 +1289,10 @@ class _SfDataGridState extends State { } } - if (isSourceChanged || isDataSourceChanged || isFrozenRowPaneChanged) { + if (isSourceChanged || + isDataSourceChanged || + isFrozenRowPaneChanged || + isStackedHeaderRowsChanged) { _container._refreshView(); } @@ -1080,6 +1337,18 @@ class _SfDataGridState extends State { } } + void _onStackedHeaderRowsPropertyChanged( + SfDataGrid oldWidget, SfDataGrid widget) { + _container._refreshHeaderLineCount(); + if (oldWidget.stackedHeaderRows.isNotEmpty) { + _container.rowGenerator.items + .removeWhere((row) => row.rowType == RowType.stackedHeaderRow); + } + if (widget.onQueryRowHeight != null) { + _container.rowHeightManager.reset(); + } + } + void _ensureSelectionProperties() { if (_controller.selectedRows.isNotEmpty) { _rowSelectionManager?.onSelectedRowsChanged(); @@ -1118,12 +1387,21 @@ class _SfDataGridState extends State { _dataGridSettings.boxPainter = decoration.createBoxPainter(); } + void _addDataGridSourceListeners() { + _source?.addListener(_handleDataSourceChangeListeners); + _source?.addListener(_handleNotifyListeners); + } + + void _removeDataGridSourceListeners() { + _source?.removeListener(_handleDataSourceChangeListeners); + _source?.removeListener(_handleNotifyListeners); + } + @override void didChangeDependencies() { textDirection = Directionality.of(context); dataGridThemeData = SfDataGridTheme.of(context); - _dataGridSettings.textScaleFactor = - MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0; + _dataGridSettings.textScaleFactor = MediaQuery.textScaleFactorOf(context); _dataGridSettings.headerRowHeight = widget.headerRowHeight ?? (_dataGridSettings.textScaleFactor > 1.0 ? 56.0 * _dataGridSettings.textScaleFactor @@ -1132,6 +1410,14 @@ class _SfDataGridState extends State { (_dataGridSettings.textScaleFactor > 1.0 ? 49.0 * _dataGridSettings.textScaleFactor : 49.0); + final ThemeData themeData = Theme.of(context); + _dataGridSettings._isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows; + _dataGridSettings.defaultColumnWidth = + widget.defaultColumnWidth ?? (_dataGridSettings._isDesktop ? 100 : 90); + _dataGridSettings.visualDensity = themeData.visualDensity; + _updateVisualDensity(); super.didChangeDependencies(); } @@ -1143,7 +1429,7 @@ class _SfDataGridState extends State { @override Widget build(BuildContext context) { - if (kIsWeb) { + if (_dataGridSettings._isDesktop) { _updateBoxPainter(); } @@ -1174,8 +1460,8 @@ class _SfDataGridState extends State { @override void dispose() { - _source?.removeListener(_handleDataGridSourceChanged); - _controller?.removeListener(_handleDataGridSourceChanged); + _removeDataGridSourceListeners(); + _controller?.removeListener(_handleDataSourceChangeListeners); _dataGridSettings ..gridPaint = null ..boxPainter = null @@ -1204,6 +1490,7 @@ class _DataGridSettings { QueryRowHeightCallback onQueryRowHeight; TextDirection textDirection; GridLinesVisibility gridLinesVisibility; + GridLinesVisibility headerGridLinesVisibility; ColumnWidthMode columnWidthMode; ColumnSizer columnSizer; ColumnWidthCalculationMode columnWidthCalculationMode; @@ -1220,6 +1507,7 @@ class _DataGridSettings { ScrollController verticalController; ScrollController horizontalController; FocusNode dataGridFocusNode; + VisualDensity visualDensity; double viewWidth; double viewHeight; Paint gridPaint; @@ -1239,4 +1527,10 @@ class _DataGridSettings { bool showSortNumbers; SortingGestureType sortingGestureType; bool isControlKeyPressed; + bool _isDesktop; + List stackedHeaderRows; + bool isScrollbarAlwaysShown; + ScrollPhysics horizontalScrollPhysics; + ScrollPhysics verticalScrollPhysics; + LoadMoreViewBuilder loadMoreViewBuilder; } diff --git a/packages/syncfusion_flutter_datepicker/CHANGELOG.md b/packages/syncfusion_flutter_datepicker/CHANGELOG.md index baa078dd0..18cce4b25 100644 --- a/packages/syncfusion_flutter_datepicker/CHANGELOG.md +++ b/packages/syncfusion_flutter_datepicker/CHANGELOG.md @@ -1,6 +1,21 @@ +## [13.4.30-beta] +**Features** +* Hijri date picker support is provided. +* The custom builder support is provided for the month cells and year cells in the date range picker. +* Vertical date picker support provided. + +## [18.3.47-beta] - 11/03/2020 +**Bug fixes** +* Now, the 24th-month date view in the `SfDateRangePicker` is rendering correctly. + +## [18.3.44-beta] - 10/27/2020 +**Bug fixes** +* Now, the `startRangeSelectionColor` and `endRangeSelectionColor` have been applied properly to the range selection of month view with `selectionShape` as a rectangle. +* Now, when defining the image for the `cellDecoration` property, the 'DateRangePickerMonthCellStyle' works correctly. + ## [18.3.35-beta] - 10/01/2020 **Bug fixes** -* Now, the `endDate` of `PickerDateRange` will not prior to the `startDate` in the `DateRangePickerSelectionChangedArgs`. +* Now, the `endDate` of `PickerDateRange` in `DateRangePickerSelectionChangedArgs` will not be set before the `startDate`. ## [18.2.59-beta] - 09/23/2020 No changes. diff --git a/packages/syncfusion_flutter_datepicker/README.md b/packages/syncfusion_flutter_datepicker/README.md index e79e849ee..a44f65798 100644 --- a/packages/syncfusion_flutter_datepicker/README.md +++ b/packages/syncfusion_flutter_datepicker/README.md @@ -33,6 +33,14 @@ The Syncfusion Flutter Date Range Picker is a lightweight widget that allows use ![multi_date_picker_view](https://cdn.syncfusion.com/content/images/FTControl/Flutter/flutter-daterangepicker-multidatepicker.png) +* **Vertical picker** - Displays two Date Range Pickers side by side in a vertical direction, allowing you to select ranges of dates within two separate months easily. + +![vertical_date_picker](https://cdn.syncfusion.com/content/images/FTControl/date+range+picker/side_by_side.png) + +* **Hijri date picker** - In addition to the Gregorian calendar, the date range picker supports displaying the Islamic calendar (Hijri date picker). + +![hijri_date_picker](https://cdn.syncfusion.com/content/images/FTControl/date+range+picker/hijricalendar.png) + * **Quick navigation** - Navigate back and forth the date-range views and between different view modes. * **Enable/disable built-in view switching** - Restrict users from navigating to different picker views by disabling view switching. Select values in terms of month, year, or decade with this feature enabled. @@ -59,6 +67,10 @@ The Syncfusion Flutter Date Range Picker is a lightweight widget that allows use * **Appearance customization** - Change the look and feel of the date range picker by customizing its default appearance and style using Flutter decorations. +* **Builder** - Allows you to design and set your own custom view to the month and year cells of the date range picker. + +![builders_in_datepicker](https://cdn.syncfusion.com/content/images/FTControl/date+range+picker/cell_builder.png) + * **Right to left(RTL)** - Right-to-left direction support for users working in RTL languages like Hebrew and Arabic. ![right_to_left](https://cdn.syncfusion.com/content/images/FTControl/Flutter/daterangepicker/right_to_left.png) diff --git a/packages/syncfusion_flutter_datepicker/analysis_options.yaml b/packages/syncfusion_flutter_datepicker/analysis_options.yaml index 2f547af5d..221601219 100644 --- a/packages/syncfusion_flutter_datepicker/analysis_options.yaml +++ b/packages/syncfusion_flutter_datepicker/analysis_options.yaml @@ -2,4 +2,5 @@ include: package:syncfusion_flutter_core/analysis_options.yaml analyzer: errors: - include_file_not_found: ignore \ No newline at end of file + include_file_not_found: ignore + diff --git a/packages/syncfusion_flutter_datepicker/lib/datepicker.dart b/packages/syncfusion_flutter_datepicker/lib/datepicker.dart index f1ff3af75..9c9240690 100644 --- a/packages/syncfusion_flutter_datepicker/lib/datepicker.dart +++ b/packages/syncfusion_flutter_datepicker/lib/datepicker.dart @@ -12,48 +12,6 @@ library datepicker; -import 'dart:ui'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/services.dart'; - -import 'package:intl/intl.dart' show DateFormat; -import 'package:flutter/scheduler.dart'; -import 'package:syncfusion_flutter_core/core.dart'; -import 'package:syncfusion_flutter_core/core_internal.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; - -/// Date picker -part 'src/date_picker/date_picker.dart'; -part 'src/date_picker/picker_settings.dart'; -part 'src/date_picker/picker_controller.dart'; - -/// Looping layout -part 'src/date_picker/looping_widget/picker_scroll_view.dart'; -part 'src/date_picker/picker_view.dart'; - -/// Header view -part 'src/date_picker/header_view/picker_header_view.dart'; -part 'src/date_picker/header_view/picker_header_painter.dart'; -part 'src/date_picker/header_view/picker_view_header.dart'; - -/// Month view -part 'src/date_picker/month_view/interface.dart'; -part 'src/date_picker/month_view/helper.dart'; -part 'src/date_picker/month_view/single_selection.dart'; -part 'src/date_picker/month_view/multi_selection.dart'; -part 'src/date_picker/month_view/range_selection.dart'; -part 'src/date_picker/month_view/multi_range_selection.dart'; - -/// Year, decade, century view -part 'src/date_picker/year_view/interface.dart'; -part 'src/date_picker/year_view/helper.dart'; -part 'src/date_picker/year_view/year_view.dart'; -part 'src/date_picker/year_view/decade_view.dart'; -part 'src/date_picker/year_view/century_view.dart'; - -/// Helper -part 'src/date_picker/common/date_picker_helper.dart'; -part 'src/date_picker/common/date_time_helper.dart'; +export 'src/date_picker/date_picker.dart'; +export 'src/date_picker/date_picker_manager.dart'; +export 'src/date_picker/hijri_date_picker_manager.dart'; diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/common/date_picker_helper.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/common/date_picker_helper.dart deleted file mode 100644 index d711b89ec..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/common/date_picker_helper.dart +++ /dev/null @@ -1,133 +0,0 @@ -part of datepicker; - -/// Selection modes for [SfDateRangePicker]. -enum DateRangePickerSelectionMode { - /// - DateRangePickerSelectionMode.single, Allows to select a single date, - /// selecting a new date will remove the selection for previous date and - /// updates selection to the new selected date. - single, - - /// - DateRangePickerSelection.multiple, Allows multiple date selection, - /// selecting a new date will not remove the selection for previous dates, - /// allows to select as many dates as possible. - multiple, - - /// - DateRangePickerSelection.range, Allows to select a single range of - /// dates. - /// See also: [PickerDateRange] - range, - - /// - DateRangePickerSelection.multiRange, Allows to select a multiple ranges - /// of dates. - /// - /// See also: [PickerDateRange]. - multiRange -} - -/// Available views for [SfDateRangePicker]. -enum DateRangePickerView { - /// - DateRangePickerView.month, Displays the month view. - month, - - /// - DateRangePickerView.year, Displays the year view. - year, - - /// - DateRangePickerView.decade, Displays the decade view. - decade, - - /// - DateRangePickerView.century, Display the century view. - century -} - -/// The shape for the selection view in [SfDateRangePicker]. -enum DateRangePickerSelectionShape { - /// - DateRangePickerSelectionShape.circle, Draws the date selection in circle - /// shape. - circle, - - /// - DateRangePickerSelectionShape.rectangle, Draws the date selection in - /// rectangle shape. - rectangle -} - -/// A direction in which the [SfDateRangePicker] navigates. -enum DateRangePickerNavigationDirection { - /// - DateRangePickerNavigationDirection.vertical, Navigates in top and bottom - /// direction. - vertical, - - /// - DateRangePickerNavigationDirection.horizontal, Navigates in right and - /// left direction. - horizontal -} - -/// args to update the required properties from picker state to it's children's -class _PickerStateArgs { - DateTime _currentDate; - List _currentViewVisibleDates; - DateTime _selectedDate; - List _selectedDates; - PickerDateRange _selectedRange; - List _selectedRanges; - DateRangePickerView _view; -} - -/// The dates that visible on the view changes in [SfDateRangePicker]. -/// -/// Details for [DateRangePickerViewChangedCallback], such as [visibleDateRange] -/// and [view]. -@immutable -class DateRangePickerViewChangedArgs { - /// Creates details for [DateRangePickerViewChangedCallback]. - const DateRangePickerViewChangedArgs(this.visibleDateRange, this.view); - - /// The date range of the currently visible view dates. - /// - /// See also: [PickerDateRange]. - final PickerDateRange visibleDateRange; - - /// The currently visible [DateRangePickerView] in the [SfDateRangePicker]. - /// - /// See also: [DateRangePickerView]. - final DateRangePickerView view; -} - -/// The selected dates or ranges changes in the [SfDateRangePicker]. -/// -/// Details for [DateRangePickerSelectionChangedCallback], such as selected -/// value. -@immutable -class DateRangePickerSelectionChangedArgs { - /// Creates details for [DateRangePickerSelectionChangedCallback]. - const DateRangePickerSelectionChangedArgs(this.value); - - /// The changed selected dates or ranges value. - /// - /// The argument value will return the changed date as [DateTime] when the - /// widget [DateRangePickerSelectionMode] set as single. - /// - /// The argument value will return the changed dates as [List] when - /// the widget [DateRangePickerSelectionMode] set as multiple. - /// - /// The argument value will return the changed range as [PickerDateRange] - /// when the widget [DateRangePickerSelectionMode] set as range. - /// - /// The argument value will return the changed ranges as - /// [List rangeCollection1, - List rangeCollection2) { - if (rangeCollection1 == rangeCollection2) { - return true; - } - - if ((rangeCollection1 == null && - rangeCollection2 != null && - rangeCollection2.isEmpty) || - (rangeCollection2 == null && - rangeCollection1 != null && - rangeCollection1.isEmpty)) { - return true; - } - - if ((rangeCollection1 == null && rangeCollection2 != null) || - (rangeCollection2 == null && rangeCollection1 != null) || - (rangeCollection1.length != rangeCollection2.length)) { - return false; - } - - for (int i = 0; i < rangeCollection1.length; i++) { - if (!_isRangeEquals(rangeCollection1[i], rangeCollection2[i])) { - return false; - } - } - - return true; -} - -//// Calculate the next view visible start date based on picker view. -DateTime _getNextViewStartDate(DateRangePickerView pickerView, - int numberOfWeeksInView, DateTime date, bool isRtl) { - if (pickerView == null) { - return date; - } - - if (isRtl != null && isRtl) { - return _getPreviousViewStartDate( - pickerView, numberOfWeeksInView, date, false); - } - - switch (pickerView) { - case DateRangePickerView.month: - { - return numberOfWeeksInView == 6 - ? getNextMonthDate(date) - : addDuration(date, - Duration(days: numberOfWeeksInView * _kNumberOfDaysInWeek)); - } - case DateRangePickerView.year: - { - return _getNextYearDate(date, 1); - } - case DateRangePickerView.decade: - { - return _getNextYearDate(date, 10); - } - case DateRangePickerView.century: - { - return _getNextYearDate(date, 100); - } - } - - return date; -} - -//// Calculate the previous view visible start date based on calendar view. -DateTime _getPreviousViewStartDate(DateRangePickerView pickerView, - int numberOfWeeksInView, DateTime date, bool isRtl) { - if (pickerView == null) { - return date; - } - - if (isRtl != null && isRtl) { - return _getNextViewStartDate(pickerView, numberOfWeeksInView, date, false); - } - - switch (pickerView) { - case DateRangePickerView.month: - { - return numberOfWeeksInView == 6 - ? getPreviousMonthDate(date) - : addDuration(date, - Duration(days: -numberOfWeeksInView * _kNumberOfDaysInWeek)); - } - case DateRangePickerView.year: - { - return _getPreviousYearDate(date, 1); - } - case DateRangePickerView.decade: - { - return _getPreviousYearDate(date, 10); - } - case DateRangePickerView.century: - { - return _getPreviousYearDate(date, 100); - } - } - - return date; -} - -DateTime _getNextYearDate(DateTime date, int offset) { - return DateTime(((date.year ~/ offset) * offset) + offset, 1, 1); -} - -DateTime _getPreviousYearDate(DateTime date, int offset) { - return DateTime(((date.year ~/ offset) * offset) - offset, 1, 1); -} - -DateTime _getMonthStartDate(DateTime date) { - return DateTime(date.year, date.month, 1); -} - -DateTime _getMonthEndDate(DateTime date) { - return subtractDuration(getNextMonthDate(date), const Duration(days: 1)); -} - -int _isDateIndexInCollection(List dates, DateTime date) { - if (dates == null || date == null) { - return -1; - } - - for (int i = 0; i < dates.length; i++) { - final DateTime visibleDate = dates[i]; - if (isSameDate(visibleDate, date)) { - return i; - } - } - - return -1; -} - -bool _isDateCollectionEquals( - List datesCollection1, List datesCollection2) { - if (datesCollection1 == datesCollection2) { - return true; - } - - if ((datesCollection1 == null && - datesCollection2 != null && - datesCollection2.isEmpty) || - (datesCollection2 == null && - datesCollection1 != null && - datesCollection1.isEmpty)) { - return false; - } - - if ((datesCollection1 == null && datesCollection2 != null) || - (datesCollection2 == null && datesCollection1 != null) || - (datesCollection1.length != datesCollection2.length)) { - return false; - } - - for (int i = 0; i < datesCollection1.length; i++) { - if (!isSameDate(datesCollection1[i], datesCollection2[i])) { - return false; - } - } - - return true; -} - -bool _isEnabledDate( - DateTime startDate, DateTime endDate, bool enablePastDates, DateTime date) { - return isDateWithInDateRange(startDate, endDate, date) && - (enablePastDates || - (!enablePastDates && - isDateWithInDateRange(DateTime.now(), endDate, date))); -} - -bool _isDateAsCurrentMonthDate(DateTime visibleDate, int rowCount, - bool showLeadingAndTrialingDates, DateTime date) { - if (rowCount == 6 && - !showLeadingAndTrialingDates && - date.month != visibleDate.month) { - return false; - } - - return true; -} - -Map _getTopAndLeftValues(bool isRtl, double left, double top, - double cellWidth, double cellHeight, double width) { - final Map topAndLeft = { - 'left': left, - 'top': top - }; - if (isRtl) { - if (left.round() == cellWidth.round()) { - left = 0; - } else { - left -= cellWidth; - } - if (left < 0) { - left = width - cellWidth; - top += cellHeight; - } - } else { - left += cellWidth; - if (left + 1 >= width) { - top += cellHeight; - left = 0; - } - } - topAndLeft['left'] = left; - topAndLeft['top'] = top; - - return topAndLeft; -} - -bool _isDateWithInVisibleDates( - List visibleDates, List dates, DateTime date) { - if (dates == null || dates.isEmpty) { - return false; - } - - final DateTime visibleStartDate = visibleDates[0]; - final DateTime visibleEndDate = visibleDates[visibleDates.length - 1]; - for (final DateTime currentDate in dates) { - if (!isDateWithInDateRange(visibleStartDate, visibleEndDate, currentDate)) { - continue; - } - - if (isSameDate(currentDate, date)) { - return true; - } - } - - return false; -} - -bool _isWeekend(List weekendIndex, DateTime date) { - if (weekendIndex == null || weekendIndex.isEmpty) { - return false; - } - - return weekendIndex.contains(date.weekday); -} - -bool _canMoveToPreviousViewRtl( - DateRangePickerView view, - int numberOfWeeksInView, - DateTime minDate, - DateTime maxDate, - List visibleDates, - bool isRtl, - bool enableMultiView) { - if (isRtl) { - return _canMoveToNextView( - view, numberOfWeeksInView, maxDate, visibleDates, enableMultiView); - } else { - return _canMoveToPreviousView( - view, numberOfWeeksInView, minDate, visibleDates, enableMultiView); - } -} - -bool _canMoveToNextViewRtl( - DateRangePickerView view, - int numberOfWeeksInView, - DateTime minDate, - DateTime maxDate, - List visibleDates, - bool isRtl, - bool enableMultiView) { - if (isRtl) { - return _canMoveToPreviousView( - view, numberOfWeeksInView, minDate, visibleDates, enableMultiView); - } else { - return _canMoveToNextView( - view, numberOfWeeksInView, maxDate, visibleDates, enableMultiView); - } -} - -bool _canMoveToPreviousView(DateRangePickerView view, int numberOfWeeksInView, - DateTime minDate, List visibleDates, bool enableMultiView) { - switch (view) { - case DateRangePickerView.month: - { - if (numberOfWeeksInView != 6) { - DateTime prevViewDate = visibleDates[0]; - prevViewDate = - subtractDuration(prevViewDate, const Duration(days: 1)); - if (!isSameOrAfterDate(minDate, prevViewDate)) { - return false; - } - } else { - final DateTime currentDate = visibleDates[visibleDates.length ~/ - (enableMultiView != null && enableMultiView ? 4 : 2)]; - final DateTime previousDate = getPreviousMonthDate(currentDate); - if ((previousDate.month < minDate.month && - previousDate.year == minDate.year) || - previousDate.year < minDate.year) { - return false; - } - } - } - break; - case DateRangePickerView.year: - case DateRangePickerView.decade: - case DateRangePickerView.century: - { - final int currentYear = visibleDates[visibleDates.length ~/ - (enableMultiView != null && enableMultiView ? 4 : 2)] - .year; - final int offset = _getOffset(view); - if (((currentYear ~/ offset) * offset) - offset < - ((minDate.year ~/ offset) * offset)) { - return false; - } - } - } - - return true; -} - -int _getOffset(DateRangePickerView view) { - switch (view) { - case DateRangePickerView.month: - break; - case DateRangePickerView.year: - return 1; - case DateRangePickerView.decade: - return 10; - case DateRangePickerView.century: - return 100; - } - return 0; -} - -//// Get the visible dates based on the date value and visible dates count. -List _getVisibleYearDates(DateTime date, DateRangePickerView view) { - final List datesCollection = []; - DateTime currentDate; - const int daysCount = 12; - switch (view) { - case DateRangePickerView.month: - break; - case DateRangePickerView.year: - { - for (int i = 1; i <= daysCount; i++) { - currentDate = DateTime(date.year, i, 1); - datesCollection.add(currentDate); - } - } - break; - case DateRangePickerView.decade: - { - final int year = (date.year ~/ 10) * 10; - for (int i = 0; i < daysCount; i++) { - currentDate = DateTime(year + i, 1, 1); - datesCollection.add(currentDate); - } - } - break; - case DateRangePickerView.century: - { - final int year = (date.year ~/ 100) * 100; - for (int i = 0; i < daysCount; i++) { - currentDate = DateTime(year + (i * 10), 1, 1); - datesCollection.add(currentDate); - } - } - } - - return datesCollection; -} - -bool _canMoveToNextView(DateRangePickerView view, int numberOfWeeksInView, - DateTime maxDate, List visibleDates, bool enableMultiView) { - switch (view) { - case DateRangePickerView.month: - { - if (numberOfWeeksInView != 6) { - DateTime nextViewDate = visibleDates[visibleDates.length - 1]; - nextViewDate = addDuration(nextViewDate, const Duration(days: 1)); - if (!isSameOrBeforeDate(maxDate, nextViewDate)) { - return false; - } - } else { - final DateTime currentDate = visibleDates[visibleDates.length ~/ - (enableMultiView != null && enableMultiView ? 4 : 2)]; - final DateTime nextDate = getNextMonthDate(currentDate); - if ((nextDate.month > maxDate.month && - nextDate.year == maxDate.year) || - nextDate.year > maxDate.year) { - return false; - } - } - } - break; - case DateRangePickerView.year: - case DateRangePickerView.decade: - case DateRangePickerView.century: - { - final int currentYear = visibleDates[visibleDates.length ~/ - (enableMultiView != null && enableMultiView ? 4 : 2)] - .year; - final int offset = _getOffset(view); - - if (((currentYear ~/ offset) * offset) + offset > - ((maxDate.year ~/ offset) * offset)) { - return false; - } - } - } - return true; -} 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 5aa6117af..5def1c93f 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 @@ -1,10 +1,23 @@ -part of datepicker; +import 'dart:ui'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/core_internal.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'date_picker_manager.dart'; +import 'hijri_date_picker_manager.dart'; +import 'month_view.dart'; +import 'picker_helper.dart'; +import 'year_view.dart'; -/// The number of days in a week -const int _kNumberOfDaysInWeek = 7; - -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. @@ -16,19 +29,31 @@ typedef _UpdatePickerState = void Function( typedef DateRangePickerViewChangedCallback = void Function( DateRangePickerViewChangedArgs dateRangePickerViewChangedArgs); +/// Signature for callback that reports that a current view or current visible +/// date range changes. +/// +/// The visible date range and the visible view which visible on view when the +/// view changes available in the [HijriDatePickerViewChangedArgs]. +/// +/// Used by [SfHijriDateRangePicker.onViewChanged]. +typedef HijriDatePickerViewChangedCallback = void Function( + HijriDatePickerViewChangedArgs hijriDatePickerViewChangedArgs); + /// Signature for callback that reports that a new dates or date ranges /// selected. /// /// The dates or ranges that selected when the selection changes available in /// the [DateRangePickerSelectionChangedArgs]. /// -/// Used by [SfDateRangePicker.onSelectionChanged]. +/// Used by [SfDateRangePicker.onSelectionChanged] and +/// [SfHijriDateRangePicker.onSelectionChanged]. 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}) { if (picker.onSelectionChanged == null) { return; } @@ -38,13 +63,19 @@ void _raiseSelectionChangedCallback(SfDateRangePicker picker, {dynamic value}) { // method that raises the visible dates changed call back with the given // parameters. -void _raisePickerViewChangedCallback(SfDateRangePicker picker, - {PickerDateRange visibleDateRange, DateRangePickerView view}) { +void _raisePickerViewChangedCallback(_SfDateRangePicker picker, + {dynamic visibleDateRange, dynamic view}) { if (picker.onViewChanged == null) { return; } - picker.onViewChanged(DateRangePickerViewChangedArgs(visibleDateRange, view)); + if (picker.isHijri) { + picker + .onViewChanged(HijriDatePickerViewChangedArgs(visibleDateRange, view)); + } else { + picker + .onViewChanged(DateRangePickerViewChangedArgs(visibleDateRange, view)); + } } /// A material design date range picker. @@ -58,8 +89,8 @@ void _raisePickerViewChangedCallback(SfDateRangePicker picker, /// mode. /// /// Set the [view] property with the desired [DateRangePickerView] to navigate -/// to different views, or tap the [SfCalendar] header to navigate to the next -/// different view in the hierarchy. +/// to different views, or tap the [SfDateRangePicker] header to navigate to the +/// next different view in the hierarchy. /// /// The hierarchy of views is followed by /// * [DateRangePickerView.month] @@ -108,7 +139,7 @@ void _raisePickerViewChangedCallback(SfDateRangePicker picker, ///class MyApp extends StatefulWidget { /// @override /// MyAppState createState() => MyAppState(); -//} +///} /// ///class MyAppState extends State { /// @override @@ -134,7 +165,7 @@ void _raisePickerViewChangedCallback(SfDateRangePicker picker, ///} /// ``` @immutable -class SfDateRangePicker extends StatefulWidget { +class SfDateRangePicker extends StatelessWidget { /// Creates a material design date range picker. /// /// To restrict the date navigation and selection interaction use [minDate], @@ -182,8 +213,12 @@ class SfDateRangePicker extends StatefulWidget { this.selectionTextStyle, this.rangeTextStyle, this.monthFormat, + this.cellBuilder, }) : headerStyle = headerStyle ?? - (enableMultiView != null && enableMultiView + (enableMultiView != null && + enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.horizontal ? DateRangePickerHeaderStyle(textAlign: TextAlign.center) : DateRangePickerHeaderStyle()), yearCellStyle = yearCellStyle ?? DateRangePickerYearCellStyle(), @@ -205,7 +240,7 @@ class SfDateRangePicker extends StatefulWidget { : initialSelectedRanges, view = controller != null && controller.view != null ? controller.view - : (view ?? DateRangePickerView.month), + : view ?? DateRangePickerView.month, monthViewSettings = monthViewSettings ?? DateRangePickerMonthViewSettings(), initialDisplayDate = @@ -437,6 +472,71 @@ class SfDateRangePicker extends StatefulWidget { /// ``` final bool allowViewNavigation; + /// A builder that builds a widget that replaces the cell in a month, year, + /// decade and century views. The month cell, year cell, decade cell, + /// century cell was differentiated by picker view. + /// + /// ```dart + /// + /// DateRangePickerController _controller; + /// + /// @override + /// void initState() { + /// _controller = DateRangePickerController(); + /// _controller.view = DateRangePickerView.month; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// title: const Text('Date range picker'), + /// ), + /// body: SfDateRangePicker( + /// controller: _controller, + /// cellBuilder: + /// (BuildContext context, DateRangePickerCellDetails cellDetails) { + /// if (_controller.view == DateRangePickerView.month) { + /// return Container( + /// width: cellDetails.bounds.width, + /// height: cellDetails.bounds.height, + /// alignment: Alignment.center, + /// child: Text(cellDetails.date.day.toString()), + /// ); + /// } else if (_controller.view == DateRangePickerView.year) { + /// return Container( + /// width: cellDetails.bounds.width, + /// height: cellDetails.bounds.height, + /// alignment: Alignment.center, + /// child: Text(cellDetails.date.month.toString()), + /// ); + /// } else if (_controller.view == DateRangePickerView.decade) { + /// return Container( + /// width: cellDetails.bounds.width, + /// height: cellDetails.bounds.height, + /// alignment: Alignment.center, + /// child: Text(cellDetails.date.year.toString()), + /// ); + /// } else { + /// final int yearValue = (cellDetails.date.year ~/ 10) * 10; + /// return Container( + /// width: cellDetails.bounds.width, + /// height: cellDetails.bounds.height, + /// alignment: Alignment.center, + /// child: Text( + /// yearValue.toString() + ' - ' + (yearValue + 9).toString()), + /// ); + /// } + /// }, + /// ), + /// )); + /// } + /// + /// ``` + final DateRangePickerCellBuilder cellBuilder; + /// Used to enable or disable showing multiple views /// /// When setting this [enableMultiView] property set to [true] displaying @@ -958,6 +1058,7 @@ class SfDateRangePicker extends StatefulWidget { /// this min date as a display date and render dates based on the date set to /// this property. /// + /// /// See also: /// [initialDisplayDate]. /// [maxDate]. @@ -979,7 +1080,7 @@ class SfDateRangePicker extends StatefulWidget { /// ``` final DateTime minDate; - /// The minimum date as much as the [SfDateRangePicker] will navigate. + /// The maximum date as much as the [SfDateRangePicker] will navigate. /// /// The [SfDateRangePicker] widget will navigate as maximum as to the given /// date, and the dates after that date will be disabled for interaction and @@ -992,6 +1093,8 @@ class SfDateRangePicker extends StatefulWidget { /// this max date as a display date and render dates based on the date set to /// this property. /// + /// See also: + /// /// [initialDisplayDate]. /// [minDate]. /// [controller.displayDate]. @@ -1026,7 +1129,7 @@ class SfDateRangePicker extends StatefulWidget { /// return MaterialApp( /// home: Scaffold( /// body: SfDateRangePicker( - // view: DateRangePickerView.month, + /// view: DateRangePickerView.month, /// enablePastDates: false, /// ), /// ), @@ -1424,630 +1527,7040 @@ class SfDateRangePicker extends StatefulWidget { final DateRangePickerSelectionChangedCallback onSelectionChanged; @override - _SfDateRangePickerState createState() => _SfDateRangePickerState(); + Widget build(BuildContext context) { + return _SfDateRangePicker( + key: key, + view: view, + selectionMode: selectionMode, + headerHeight: headerHeight, + todayHighlightColor: todayHighlightColor, + backgroundColor: backgroundColor, + initialSelectedDate: initialSelectedDate, + initialSelectedDates: initialSelectedDates, + initialSelectedRange: initialSelectedRange, + initialSelectedRanges: initialSelectedRanges, + toggleDaySelection: toggleDaySelection, + enablePastDates: enablePastDates, + showNavigationArrow: showNavigationArrow, + selectionShape: selectionShape, + navigationDirection: navigationDirection, + controller: controller, + onViewChanged: onViewChanged, + onSelectionChanged: onSelectionChanged, + headerStyle: headerStyle, + yearCellStyle: yearCellStyle, + monthViewSettings: monthViewSettings, + initialDisplayDate: initialDisplayDate, + minDate: minDate, + maxDate: maxDate, + monthCellStyle: monthCellStyle, + allowViewNavigation: allowViewNavigation, + enableMultiView: enableMultiView, + viewSpacing: viewSpacing, + selectionRadius: selectionRadius, + selectionColor: selectionColor, + startRangeSelectionColor: startRangeSelectionColor, + endRangeSelectionColor: endRangeSelectionColor, + rangeSelectionColor: rangeSelectionColor, + selectionTextStyle: selectionTextStyle, + rangeTextStyle: rangeTextStyle, + monthFormat: monthFormat, + cellBuilder: cellBuilder, + ); + } } -class _SfDateRangePickerState extends State { - List _currentViewVisibleDates; - DateTime _currentDate, _selectedDate; - double _minWidth, _minHeight; - double _textScaleFactor; - ValueNotifier> _headerVisibleDates; - List _selectedDates; - PickerDateRange _selectedRange; - List _selectedRanges; - GlobalKey<_PickerScrollViewState> _scrollViewKey; - DateRangePickerView _view; - bool _isRtl; - DateRangePickerController _controller; - Locale _locale; - SfDateRangePickerThemeData _datePickerTheme; +/// A material design date range picker. +/// +/// A [SfHijriDateRangePicker] can be used to select single date, multiple +/// dates, and range of dates in month view alone and provided month, year +/// and decade view options to quickly navigate to the desired date, it +/// supports [minDate],[maxDate] and disabled date to restrict the date +/// selection. +/// +/// Default displays the [HijriDatePickerView.month] view with single +/// selection mode. +/// +/// Set the [view] property with the desired [HijriDatePickerView] to +/// navigate to different views, or tap the [SfHijriDateRangePicker] header to +/// navigate to the next different view in the hierarchy. +/// +/// The hierarchy of views is followed by +/// * [HijriDatePickerView.month] +/// * [HijriDatePickerView.year] +/// * [HijriDatePickerView.decade] +/// +/// To change the selection mode, set the [selectionMode] property with the +/// [DateRangePickerSelectionMode] option. +/// +/// To restrict the date navigation and selection interaction use [minDate], +/// [maxDate], the dates beyond this will be restricted. +/// +/// When the selected dates or ranges change, the widget will call the +/// [onSelectionChanged] callback with new selected dates or ranges. +/// +/// When the visible view changes, the widget will call the [onViewChanged] +/// callback with the current view and the current view visible dates. +/// +/// Requires one of its ancestors to be a Material widget. This is typically +/// provided by a Scaffold widget. +/// +/// Requires one of its ancestors to be a MediaQuery widget. Typically, +/// a MediaQuery widget is introduced by the MaterialApp or WidgetsApp widget +/// at the top of your application widget tree. +/// +/// _Note:_ The picker widget allows to customize its appearance using +/// [SfDateRangePickerThemeData] available from [SfDateRangePickerTheme] widget +/// or the [SfTheme.dateRangePickerTheme] widget. +/// It can also be customized using the properties available in +/// [DateRangePickerHeaderStyle], [DateRangePickerViewHeaderStyle], +/// [HijriDatePickerMonthViewSettings], +/// [HijriDatePickerYearCellStyle], [HijriDatePickerMonthCellStyle] +/// +/// See also: +/// * [SfDateRangePickerThemeData] +/// * [DateRangePickerHeaderStyle] +/// * [DateRangePickerViewHeaderStyle] +/// * [HijriDatePickerMonthViewSettings] +/// * [HijriDatePickerYearCellStyle] +/// * [HijriDatePickerMonthCellStyle] +/// +/// ``` dart +///class MyApp extends StatefulWidget { +/// @override +/// MyAppState createState() => MyAppState(); +///} +/// +///class MyAppState extends State { +/// @override +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// home: Scaffold( +/// body: SfHijriDateRangePicker( +/// view: HijriDatePickerView.month, +/// selectionMode: DateRangePickerSelectionMode.range, +/// minDate: HijriDateTime(1440, 02, 05), +/// maxDate: HijriDateTime(1450, 12, 06), +/// onSelectionChanged: (DateRangePickerSelectionChangedArgs args) { +/// final dynamic value = args.value; +/// }, +/// onViewChanged: (HijriDatePickerViewChangedArgs args) { +/// final HijriDateRange visibleDates = args.visibleDateRange; +/// final HijriDatePickerView view = args.view; +/// }, +/// ), +/// ), +/// ); +/// } +///} +/// ``` +@immutable +class SfHijriDateRangePicker extends StatelessWidget { + /// Creates a material design date range picker. + /// + /// To restrict the date navigation and selection interaction use [minDate], + /// [maxDate], the dates beyond this will be restricted. + /// + /// When the selected dates or ranges change, the widget will call the + /// [onSelectionChanged] callback with new selected dates or ranges. + /// + /// When the visible view changes, the widget will call the [onViewChanged] + /// callback with the current view and the current view visible dates. + SfHijriDateRangePicker({ + Key key, + HijriDatePickerView view, + this.selectionMode = DateRangePickerSelectionMode.single, + this.headerHeight = 40, + this.todayHighlightColor, + this.backgroundColor, + HijriDateTime initialSelectedDate, + List initialSelectedDates, + HijriDateRange initialSelectedRange, + List initialSelectedRanges, + this.toggleDaySelection = false, + this.enablePastDates = true, + this.showNavigationArrow = false, + this.selectionShape = DateRangePickerSelectionShape.circle, + this.navigationDirection = DateRangePickerNavigationDirection.horizontal, + this.controller, + this.onViewChanged, + this.onSelectionChanged, + DateRangePickerHeaderStyle headerStyle, + HijriDatePickerYearCellStyle yearCellStyle, + HijriDatePickerMonthViewSettings monthViewSettings, + HijriDateTime initialDisplayDate, + HijriDateTime minDate, + HijriDateTime maxDate, + HijriDatePickerMonthCellStyle monthCellStyle, + bool allowViewNavigation, + bool enableMultiView, + double viewSpacing, + this.selectionRadius, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.selectionTextStyle, + this.rangeTextStyle, + this.monthFormat, + this.cellBuilder, + }) : headerStyle = headerStyle ?? + (enableMultiView != null && + enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.horizontal + ? DateRangePickerHeaderStyle(textAlign: TextAlign.center) + : DateRangePickerHeaderStyle()), + yearCellStyle = yearCellStyle ?? HijriDatePickerYearCellStyle(), + 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 ?? HijriDatePickerView.month, + monthViewSettings = + monthViewSettings ?? HijriDatePickerMonthViewSettings(), + initialDisplayDate = + controller != null && controller.displayDate != null + ? controller.displayDate + : (initialDisplayDate ?? HijriDateTime.now()), + minDate = minDate ?? HijriDateTime(1356, 01, 01), + maxDate = maxDate ?? HijriDateTime(1499, 12, 30), + monthCellStyle = monthCellStyle ?? HijriDatePickerMonthCellStyle(), + enableMultiView = enableMultiView ?? false, + viewSpacing = viewSpacing ?? + (enableMultiView != null && enableMultiView ? 20 : 0), + allowViewNavigation = allowViewNavigation ?? true, + super(key: key); - @override - void initState() { - _isRtl = false; - _scrollViewKey = GlobalKey<_PickerScrollViewState>(); - //// Update initial values to controller. - _initPickerController(); - _initNavigation(); - //// Update initial value to state variables. - _updateSelectionValues(); - _view = _controller.view; - _updateCurrentVisibleDates(); - _headerVisibleDates = - ValueNotifier>(_currentViewVisibleDates); - _controller.addPropertyChangedListener(_pickerValueChangedListener); - super.initState(); - } + /// Defines the view for the [SfHijriDateRangePicker]. + /// + /// Default to `HijriDatePickerView.month`. + /// + /// _Note:_ If the [controller] and it's [controller.view] property is not + /// null, then this property will be ignored and widget will display the view + /// described in [controller.view] property. + /// + /// Also refer [HijriDatePickerView]. + /// + /// ```dart + ///Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.year, + /// minDate: HijriDateTime(1440, 02, 05), + /// maxDate: HijriDateTime(1450, 12, 06), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final HijriDatePickerView view; - @override - void didChangeDependencies() { - _textScaleFactor = MediaQuery.of(context).textScaleFactor ?? 1.0; - final TextDirection direction = Directionality.of(context); - // default width value will be device width when the widget placed inside a - // infinity width widget - _minWidth = MediaQuery.of(context).size.width; - // default height for the widget when the widget placed inside a infinity - // height widget - _minHeight = 300; - _locale = Localizations.localeOf(context); - final SfDateRangePickerThemeData pickerTheme = - SfDateRangePickerTheme.of(context); - final ThemeData themeData = Theme.of(context); - _datePickerTheme = pickerTheme.copyWith( - todayTextStyle: pickerTheme.todayTextStyle != null && - pickerTheme.todayTextStyle.color == null - ? pickerTheme.todayTextStyle.copyWith(color: themeData.accentColor) - : pickerTheme.todayTextStyle, - todayCellTextStyle: pickerTheme.todayCellTextStyle != null && - pickerTheme.todayCellTextStyle.color == null - ? pickerTheme.todayCellTextStyle - .copyWith(color: themeData.accentColor) - : pickerTheme.todayCellTextStyle, - selectionColor: pickerTheme.selectionColor ?? themeData.accentColor, - startRangeSelectionColor: - pickerTheme.startRangeSelectionColor ?? themeData.accentColor, - rangeSelectionColor: pickerTheme.rangeSelectionColor ?? - themeData.accentColor.withOpacity(0.1), - endRangeSelectionColor: - pickerTheme.endRangeSelectionColor ?? themeData.accentColor, - todayHighlightColor: - pickerTheme.todayHighlightColor ?? themeData.accentColor); - _isRtl = direction != null && direction == TextDirection.rtl; - super.didChangeDependencies(); - } + /// Defines the selection mode for [SfHijriDateRangePicker]. + /// + /// Defaults to `DateRangePickerSelectionMode.single`. + /// + /// Also refer [DateRangePickerSelectionMode]. + /// + /// _Note:_ If it set as Range or MultiRange, the navigation through swiping + /// will be restricted by default and the navigation between views can be + /// achieved by using the navigation arrows in header view. + /// + /// If it is set as Range or MultiRange and also the + /// [HijriDatePickerMonthViewSettings.enableSwipeSelection] set as + /// [false] the navigation through swiping will work as it is without any + /// restriction. + /// + /// See also: [HijriDatePickerMonthViewSettings.enableSwipeSelection]. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// minDate: HijriDateTime(1440, 02, 05), + /// maxDate: HijriDateTime(1450, 12, 06), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final DateRangePickerSelectionMode selectionMode; - @override - void didUpdateWidget(SfDateRangePicker oldWidget) { - if (oldWidget.controller != widget.controller) { - oldWidget.controller + /// Sets the style for customizing the [SfHijriDateRangePicker] header view. + /// + /// Allows to customize the [DateRangePickerHeaderStyle.textStyle], + /// [DateRangePickerHeaderStyle.textAlign] and + /// [DateRangePickerHeaderStyle.backgroundColor] of the header view in + /// [SfHijriDateRangePicker]. + /// + /// See also: [DateRangePickerHeaderStyle] + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// headerStyle: DateRangePickerHeaderStyle( + /// textAlign: TextAlign.left, + /// textStyle: TextStyle( + /// color: Colors.blue, fontSize: 18, + /// fontWeight: FontWeight.w400), + /// backgroundColor: Colors.grey, + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final DateRangePickerHeaderStyle headerStyle; + + /// The height for header view to layout within this in + /// [SfHijriDateRangePicker]. + /// + /// Defaults to value `40`. + /// + /// _Note:_ If [showNavigationArrows] set as true the arrows will shrink or + /// grow based on the given header height value. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// headerHeight: 50, + /// showNavigationArrow: true, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final double headerHeight; + + /// Color that highlights the today date cell in [SfHijriDateRangePicker]. + /// + /// Allows to change the color that highlights the today date cell border in + /// month, year and decade view in date range picker. + /// + /// Defaults to null. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// todayHighlightColor: Colors.red, + /// showNavigationArrow: true, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Color todayHighlightColor; + + /// The color to fill the background of the [SfHijriDateRangePicker]. + /// + /// Defaults to null. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// todayHighlightColor: Colors.red, + /// backgroundColor: Colors.cyanAccent, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Color backgroundColor; + + /// Allows to deselect a date when the [DateRangePickerSelectionMode] set as + /// [DateRangePickerSelectionMode.single]. + /// + /// When this [toggleDaySelection] property set as [true] tapping on a single + /// date for the second time will clear the selection, which means setting + /// this property as [true] allows to deselect a date when the + /// [DateRangePickerSelectionMode] set as single. + /// + /// Defaults to `false`. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.single, + /// toggleDaySelection: true, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final bool toggleDaySelection; + + /// A builder that builds a widget that replaces the cell in a month, year, + /// decade and century views. The month cell, year cell, decade cell, + /// century cell was differentiated by picker view. + /// + /// ```dart + /// + /// DateRangePickerController _controller; + /// + /// @override + /// void initState() { + /// _controller = DateRangePickerController(); + /// _controller.view = DateRangePickerView.month; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// title: const Text('Date range picker'), + /// ), + /// body: SfDateRangePicker( + /// controller: _controller, + /// cellBuilder: + /// (BuildContext context, DateRangePickerCellDetails cellDetails) { + /// if (_controller.view == DateRangePickerView.month) { + /// return Container( + /// width: cellDetails.bounds.width, + /// height: cellDetails.bounds.height, + /// alignment: Alignment.center, + /// child: Text(cellDetails.date.day.toString()), + /// ); + /// } else if (_controller.view == DateRangePickerView.year) { + /// return Container( + /// width: cellDetails.bounds.width, + /// height: cellDetails.bounds.height, + /// alignment: Alignment.center, + /// child: Text(cellDetails.date.month.toString()), + /// ); + /// } else if (_controller.view == DateRangePickerView.decade) { + /// return Container( + /// width: cellDetails.bounds.width, + /// height: cellDetails.bounds.height, + /// alignment: Alignment.center, + /// child: Text(cellDetails.date.year.toString()), + /// ); + /// } else { + /// final int yearValue = (cellDetails.date.year ~/ 10) * 10; + /// return Container( + /// width: cellDetails.bounds.width, + /// height: cellDetails.bounds.height, + /// alignment: Alignment.center, + /// child: Text( + /// yearValue.toString() + ' - ' + (yearValue + 9).toString()), + /// ); + /// } + /// }, + /// ), + /// )); + /// } + /// + /// ``` + final DateRangePickerCellBuilder cellBuilder; + + /// Used to enable or disable the view switching between + /// [HijriDatePickerView] through interaction in the + /// [SfHijriDateRangePicker] header. + /// + /// Selection is allowed for year and decade views when the + /// [allowViewNavigation] property is false. + /// Otherwise, year and decade views are allowed only for view + /// navigation. + /// + /// Defaults to `true`. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// allowViewNavigation: false, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final bool allowViewNavigation; + + /// Used to enable or disable showing multiple views + /// + /// When setting this [enableMultiView] property set to [true] displaying + /// multiple views and provide quick navigation and dates selection. + /// It is applicable for all the [HijriDatePickerView] types. + /// + /// Decade view does not show trailing cells when the [enableMultiView] + /// property is enabled. + /// + /// Enabling this [enableMultiView] property is recommended for web + /// browser and larger android and iOS devices(iPad, tablet, etc.,) + /// + /// Note : Each of the views have individual header when the [textAlign] + /// property in the [headerStyle] as center + /// eg., Muharram, 1442 Safar, 1442 + /// otherwise, shown a single header for the multiple views + /// eg., Muharram, 1442 - Safar, 1442 + /// + /// Defaults to `false`. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// enableMultiView: true, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final bool enableMultiView; + + /// Used to define the space[double] between multiple views when the + /// [enableMultiView] is enabled. + /// Otherwise, the [viewSpacing] value as not applied in + /// [SfHijriDateRangePicker]. + /// + /// Defaults to value `20`. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// enableMultiView: true, + /// viewSpacing: 20, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final double viewSpacing; + + /// The radius for the [SfHijriDateRangePicker] selection circle. + /// + /// Defaults to null. + /// + /// _Note:_ This only applies if the [DateRangePickerSelectionMode] is set + /// to [DateRangePickerSelectionMode.circle]. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// selectionRadius: 20, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final double selectionRadius; + + /// The text style for the text in the selected date or dates cell of + /// [SfHijriDateRangePicker]. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// selectionTextStyle: TextStyle( + /// fontStyle: FontStyle.normal, + /// fontWeight: FontWeight.w500, + /// fontSize: 12, + /// color: Colors.white), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final TextStyle selectionTextStyle; + + /// The text style for the text in the selected range or ranges cell of + /// [SfHijriDateRangePicker] month view. + /// + /// The style applies to the dates that falls between the + /// [HijriDateRange.startDate] and [HijriDateRange.endDate]. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// _Note:_ This applies only when [DateRangePickerSelectionMode] set as + /// [DateRangePickerSelectionMode.range] or + /// [DateRangePickerSelectionMode.multiRange]. + /// + /// See also: + /// [HijriDateRange] + /// [DateRangePickerSelectionMode] + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// rangeTextStyle: TextStyle( + /// fontStyle: FontStyle.italic, + /// fontWeight: FontWeight.w500, + /// fontSize: 12, + /// color: Colors.black), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final TextStyle rangeTextStyle; + + /// The color which fills the [SfHijriDateRangePicker] selection view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// Note : It is applies only when the [DateRangePickerSelectionMode] set as + /// [DateRangePickerSelectionMode.single] of + /// [DateRangePickerSelectionMode.multiple]. + /// + /// ``` dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.multiple, + /// selectionColor: Colors.red, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Color selectionColor; + + /// The color which fills the [SfHijriDateRangePicker] selection view of the + /// range start date. + /// + /// The color fills to the selection view of the date in + /// [HijriDateRange.startDate] property. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// Note : It is applies only when the [DateRangePickerSelectionMode] set as + /// [DateRangePickerSelectionMode.range] of + /// [DateRangePickerSelectionMode.multiRange]. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// startRangeSelectionColor: Colors.red, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Color startRangeSelectionColor; + + /// The color which fills the [SfHijriDateRangePicker] selection view for the + /// range of dates which falls between the [HijriDateRange.startDate] + /// and [HijriDateRange.endDate]. + /// + /// The color fills to the selection view of the dates in between the + /// [HijriDateRange.startDate] and [HijriDateRange.endDate] + /// property. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// Note : It is applies only when the [DateRangePickerSelectionMode] set as + /// [DateRangePickerSelectionMode.range] of + /// [DateRangePickerSelectionMode.multiRange]. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// rangeSelectionColor: Colors.red.withOpacity(0.4), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Color rangeSelectionColor; + + /// The color which fills the [SfHijriDateRangePicker] selection view of the + /// range end date. + /// + /// The color fills to the selection view of the date in + /// [HijriDateRange.endDate] property. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// Note : It is applies only when the [DateRangePickerSelectionMode] set as + /// [DateRangePickerSelectionMode.range] of + /// [DateRangePickerSelectionMode.multiRange]. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// endRangeSelectionColor: Colors.red, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Color endRangeSelectionColor; + + /// Options to customize the month view of the [SfHijriDateRangePicker]. + /// + /// Allows to customize the + /// [HijriDatePickerMonthViewSettings.firstDayOfWeek], + /// [HijriDatePickerMonthViewSettings.dayFormat], + /// [HijriDatePickerMonthViewSettings.viewHeaderHeight], + /// [HijriDatePickerMonthViewSettings.viewHeaderStyle], + /// [HijriDatePickerMonthViewSettings.enableSwipeSelection], + /// [HijriDatePickerMonthViewSettings.blackoutDates], + /// [HijriDatePickerMonthViewSettings.specialDates] + /// and [HijriDatePickerMonthViewSettings.weekendDays] in month view of + /// date range picker. + /// + /// See also: [HijriDatePickerMonthViewSettings] + /// + /// ```dart + /// + ///Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// monthViewSettings: HijriDatePickerMonthViewSettings( + /// firstDayOfWeek: 1, + /// dayFormat: 'E', + /// viewHeaderHeight: 70, + /// selectionRadius: 10, + /// viewHeaderStyle: DateRangePickerViewHeaderStyle( + /// backgroundColor: Colors.blue, + /// textStyle: + /// TextStyle(fontWeight: FontWeight.w400, + /// fontSize: 15, Colors.black)), + /// enableSwipeSelection: false, + /// blackoutDates: [ + /// HijriDateTime.now().add(Duration(days: 4)) + /// ], + /// specialDates: [ + /// HijriDateTime.now().add(Duration(days: 7)), + /// HijriDateTime.now().add(Duration(days: 8)) + /// ], + /// weekendDays: [ + /// DateTime.monday, + /// DateTime.friday + /// ]), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final HijriDatePickerMonthViewSettings monthViewSettings; + + /// Options to customize the year and decade view of the + /// [SfHijriDateRangePicker]. + /// + /// Allows to customize the [HijriDatePickerYearCellStyle.textStyle], + /// [HijriDatePickerYearCellStyle.todayTextStyle], + /// [HijriDatePickerYearCellStyle.disabledDatesTextStyle], + /// [HijriDatePickerYearCellStyle.cellDecoration], + /// [HijriDatePickerYearCellStyle.todayCellDecoration], + /// [HijriDatePickerYearCellStyle.disabledDatesDecoration] in year and + /// decade view of the date range picker. + /// + /// See also: [HijriDatePickerYearCellStyle]. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.decade, + /// enablePastDates: false, + /// yearCellStyle: HijriDatePickerYearCellStyle( + /// textStyle: TextStyle( + /// fontWeight: FontWeight.w400, fontSize: 15, + /// color: Colors.black), + /// todayTextStyle: TextStyle( + /// fontStyle: FontStyle.italic, + /// fontSize: 15, + /// fontWeight: FontWeight.w500, + /// color: Colors.red), + /// disabledDatesDecoration: BoxDecoration( + /// color: const Color(0xFFDFDFDF).withOpacity(0.2), + /// border: Border.all(color: const Color(0xFFB6B6B6), + /// width: 1), + /// shape: BoxShape.circle), + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final HijriDatePickerYearCellStyle yearCellStyle; + + /// Options to customize the month cells of the [SfHijriDateRangePicker]. + /// + /// Allows to customize the [HijriDatePickerMonthCellStyle.textStyle], + /// [HijriDatePickerMonthCellStyle.todayTextStyle], + /// [HijriDatePickerMonthCellStyle.disabledDatesTextStyle], + /// [HijriDatePickerMonthCellStyle.blackoutDateTextStyle], + /// [HijriDatePickerMonthCellStyle.weekendTextStyle], + /// [HijriDatePickerMonthCellStyle.specialDatesTextStyle], + /// [HijriDatePickerMonthCellStyle.specialDatesDecoration], + /// [HijriDatePickerMonthCellStyle.blackoutDatesDecoration], + /// [HijriDatePickerMonthCellStyle.cellDecoration], + /// [HijriDatePickerMonthCellStyle.todayCellDecoration], + /// [HijriDatePickerMonthCellStyle.disabledDatesDecoration], + /// [HijriDatePickerMonthCellStyle.weekendDatesDecoration] in the month + /// cells of the date range picker. + /// + /// See also: [HijriDatePickerMonthCellStyle] + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// monthCellStyle: HijriDatePickerMonthCellStyle( + /// textStyle: TextStyle( + /// fontWeight: FontWeight.w400, fontSize: 15, + /// color: Colors.black), + /// todayTextStyle: TextStyle( + /// fontStyle: FontStyle.italic, + /// fontSize: 15, + /// fontWeight: FontWeight.w500, + /// color: Colors.red), + /// disabledDatesDecoration: BoxDecoration( + /// color: const Color(0xFFDFDFDF).withOpacity(0.2), + /// border: Border.all(color: const Color(0xFFB6B6B6), + /// width: 1), + /// shape: BoxShape.circle), + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final HijriDatePickerMonthCellStyle monthCellStyle; + + /// The initial date to show on the [SfHijriDateRangePicker] + /// + /// The [SfHijriDateRangePicker] will display the dates based on the date set + /// in this property. + /// + /// Defaults to current date. + /// + /// _Note:_ If the [controller] and it's [controller.displayDate] property is + /// not [null] then this property will be ignored and the widget render the + /// dates based on the date given in [controller.displayDate]. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// initialDisplayDate: HijriDateTime(1450, 02, 05), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final HijriDateTime initialDisplayDate; + + /// The date to initially select on the [SfHijriDateRangePicker]. + /// + /// The [SfHijriDateRangePicker] will select the date that set to this + /// property. + /// + /// Defaults to null. + /// + /// _Note:_ If the [controller] and it's [controller.selectedDate] property is + /// not [null] then this property will be ignored and the widget render the + /// selection for the date given in [controller.selectedDate]. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.single]. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// initialSelectedDate: + /// HijriDateTime.now().add((Duration(days: 5))), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final HijriDateTime initialSelectedDate; + + /// The minimum date as much as the [SfHijriDateRangePicker] will navigate. + /// + /// The [SfHijriDateRangePicker] widget will navigate as minimum as to the + /// given date, and the dates before that date will be disabled for + /// interaction and navigation to those dates were restricted. + /// + /// Defaults to `1st Muharram of 1356`. + /// + /// _Note:_ If the [initialDisplayDate] or [controller.displayDate] property + /// set with the date prior to this date, the [SfHijriDateRangePicker] will + /// take this min date as a display date and render dates based on the date + /// set to this property. + /// + /// + /// See also: + /// [initialDisplayDate]. + /// [maxDate]. + /// [controller.displayDate]. + /// [HijriDateTime]. + /// + /// ``` dart + /// + ///Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// minDate: HijriDateTime(1440, 01, 01), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final HijriDateTime minDate; + + /// The maximum date as much as the [SfHijriDateRangePicker] will navigate. + /// + /// The [SfHijriDateRangePicker] widget will navigate as maximum as to the + /// given date, and the dates after that date will be disabled for interaction + /// and navigation to those dates were restricted. + /// + /// Defaults to `30th Dhu al-Hijjah of 1499`. + /// + /// _Note:_ If the [initialDisplayDate] or [controller.displayDate] property + /// set with the date after to this date, the [SfHijriDateRangePicker] will + /// take this max date as a display date and render dates based on the date + /// set to this property. + /// + /// See also: + /// + /// [initialDisplayDate]. + /// [minDate]. + /// [controller.displayDate]. + /// [HijriDateTime]. + /// + /// ``` dart + /// + ///Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// maxDate: HijriDateTime(1450, 12, 30), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final HijriDateTime maxDate; + + /// Allows to disable the dates falls before the today date in + /// [SfHijriDateRangePicker]. + /// + /// If it is set as [false] the dates falls before the today date is disabled + /// and selection interactions to that dates were restricted. + /// + /// Defaults to `true`. + /// + /// ``` dart + /// + ///Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// ), + /// ), + /// ); + /// } + ///``` + final bool enablePastDates; + + /// The collection of dates to initially select on the + /// [SfHijriDateRangePicker]. + /// + /// If it is not [null] the [SfHijriDateRangePicker] will select the dates + /// that set to this property. + /// + /// Defaults to null. + /// + /// _Note:_ If the [controller] and it's [controller.selectedDates] property + /// is not [null] then this property will be ignored and the widget render the + /// selection for the dates given in [controller.selectedDates]. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.multiple]. + /// + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.multiple, + /// initialSelectedDates: [ + /// HijriDateTime.now().add((Duration(days: 4))), + /// HijriDateTime.now().add((Duration(days: 5))), + /// HijriDateTime.now().add((Duration(days: 9))), + /// HijriDateTime.now().add((Duration(days: 11))) + /// ], + /// ), + /// ), + /// ); + ///} + /// + /// ``` + final List initialSelectedDates; + + /// The date range to initially select on the [SfHijriDateRangePicker]. + /// + /// If it is not [null] the [SfHijriDateRangePicker] will select the range of + /// dates that set to this property. + /// + /// Defaults to null. + /// + /// _Note:_ If the [controller] and it's [controller.selectedRange] property + /// is not [null] then this property will be ignored and the widget render the + /// selection for the range given in [controller.selectedRange]. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.range]. + /// + /// See also: [HijriDateRange]. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// initialSelectedRange: HijriDateRange( + /// HijriDateTime.now().subtract((Duration(days: 4))), + /// HijriDateTime.now().add(Duration(days: 4))), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final HijriDateRange initialSelectedRange; + + /// The date ranges to initially select on the [SfHijriDateRangePicker]. + /// + /// If it is not [null] the [SfHijriDateRangePicker] will select the range of + /// dates that set to this property. + /// + /// Defaults to null. + /// + /// _Note:_ If the [controller] and it's [controller.selectedRanges] property + /// is not [null] then this property will be ignored and the widget render the + /// selection for the ranges given in [controller.selectedRanges]. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.multiRange]. + /// + /// See also: [HijriDateRange]. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// initialSelectedRanges: [ + /// HijriDateRange( + /// HijriDateTime.now().subtract(Duration(days: 4)), + /// HijriDateTime.now().add(Duration(days: 4))), + /// HijriDateRange( + /// HijriDateTime.now().add(Duration(days: 7)), + /// HijriDateTime.now().add(Duration(days: 14))) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final List initialSelectedRanges; + + /// An object that used for programmatic date navigation, date and range + /// selection and view switching in [SfHijriDateRangePicker]. + /// + /// A [HijriDatePickerController] served for several purposes. It can be + /// used to selected dates and ranges programmatically on + /// [SfHijriDateRangePicker] by using the[controller.selectedDate], + /// [controller.selectedDates], [controller.selectedRange], + /// [controller.selectedRanges]. It can be used to change the + /// [SfHijriDateRangePicker] view by using the [controller.view] property. It + /// can be used to navigate to specific date by using the + /// [controller.displayDate] property. + /// + /// ## Listening to property changes: + /// The [HijriDatePickerController] is a listenable. It notifies it's + /// listeners whenever any of attached [SfHijriDateRangePicker]`s selected + /// date, display date and view changed (i.e: selecting a different date, + /// swiping to next/previous view and navigates to different view] in in + /// [SfHijriDateRangePicker]. + /// + /// ## Navigates to different view: + /// The [SfHijriDateRangePicker] visible view can be changed by using the + /// [Controller.view] property, the property allow to change the view of + /// [SfHijriDateRangePicker] programmatically on initial load and in run time. + /// + /// ## Programmatic selection: + /// In [SfHijriDateRangePicker] selecting dates programmatically can be + /// achieved by using the [controller.selectedDate], + /// [controller.selectedDates], [controller.selectedRange], + /// [controller.selectedRanges] which allows to select the dates or ranges + /// programmatically on [SfHijriDateRangePicker] on initial load and in run + /// time. + /// + /// See also: [DateRangePickerSelectionMode] + /// + /// Defaults to null. + /// + /// This example demonstrates how to use the [HijriDatePickerController] + /// for [SfHijriDateRangePicker]. + /// + /// ``` dart + /// + ///class MyApp extends StatefulWidget { + /// @override + /// MyAppState createState() => MyAppState(); + ///} + /// + ///class MyAppState extends State { + /// HijriDatePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = HijriDatePickerController(); + /// _pickerController.selectedDates = [ + /// HijriDateTime.now().add(Duration(days: 2)), + /// HijriDateTime.now().add(Duration(days: 4)), + /// HijriDateTime.now().add(Duration(days: 7)), + /// HijriDateTime.now().add(Duration(days: 11)) + /// ]; + /// _pickerController.displayDate = HijriDateTime.now(); + /// _pickerController.addPropertyChangedListener(handlePropertyChange); + /// super.initState(); + /// } + /// + /// void handlePropertyChange(String propertyName) { + /// if (propertyName == 'selectedDates') { + /// final List selectedDates = + /// _pickerController.selectedDates; + /// } else if (propertyName == 'displayDate') { + /// final HijriDateTime displayDate = _pickerController.displayDate; + /// } + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// controller: _pickerController, + /// selectionMode: DateRangePickerSelectionMode.multiple, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + final HijriDatePickerController controller; + + /// Displays the navigation arrows on the header view of + /// [SfHijriDateRangePicker]. + /// + /// If this property set as [true] the header view of [SfHijriDateRangePicker] + /// will display the navigation arrows which used to navigate to the + /// previous/next views through the navigation icon buttons. + /// + /// defaults to `false`. + /// + /// _Note:_ If the [DateRangePickerSelectionMode] set as range or multi range + /// then the navigation arrows will be shown in the header by default, even if + /// the [showNavigationArrow] property set as [false]. + /// + /// If the [HijriDatePickerMonthViewSettings.enableSwipeSelection] set as + /// [false] the navigation arrows will be shown, only whn the + /// [showNavigationArrow] property set as [true]. + /// + /// ``` dart + /// + ///Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.multiple, + /// showNavigationArrow: true, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + final bool showNavigationArrow; + + /// The direction that [SfHijriDateRangePicker] is navigating in. + /// + /// If it this property set as [DateRangePickerNavigationDirection.vertical] + /// the [SfHijriDateRangePicker] will navigate to the previous/next views in + /// the vertical direction instead of the horizontal direction. + /// + /// Defaults to `DateRangePickerNavigationDirection.horizontal`. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.multiple, + /// showNavigationArrow: true, + /// navigationDirection: DateRangePickerNavigationDirection.vertical, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final DateRangePickerNavigationDirection navigationDirection; + + /// Defines the shape for the selection view in [SfHijriDateRangePicker]. + /// + /// If this property set as [DateRangePickerSelectionShape.rectangle] the + /// widget will render the date selection in the rectangle shape in month + /// view. + /// + /// Defaults to `DateRangePickerSelectionShape.circle`. + /// + /// _Note:_ When the [view] set with any other view than + /// [DateRangePickerView.month] the today cell highlight shape will be + /// determined by this property. + /// + /// If the [DateRangePickerSelectionShape] is set as + /// [DateRangePickerSelectionShape.circle], then the circle radius can be + /// adjusted in month view by using the [selectionRadius] property. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.multiple, + /// showNavigationArrow: true, + /// selectionShape: DateRangePickerSelectionShape.rectangle, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final DateRangePickerSelectionShape selectionShape; + + /// Allows to change the month text format in [SfHijriDateRangePicker]. + /// + /// The [SfHijriDateRangePicker] will render the month format in the month + /// view header with expanded month text format and the year view cells with + /// the short month text format by default. + /// + /// If it is not [null] then the month view header and the year view cells + /// month text will be formatted based on the format given in this property. + /// + /// Defaults to null. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.multiple, + /// showNavigationArrow: true, + /// monthFormat: 'EEE', + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final String monthFormat; + + /// Called when the current visible view or visible date range changes. + /// + /// The visible date range and the visible view which visible on view when the + /// view changes available in the [HijriDatePickerViewChangedArgs]. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.multiple, + /// showNavigationArrow: true, + /// onViewChanged: (HijriDatePickerViewChangedArgs args) { + /// final HijriDateRange _visibleDateRange = + /// args.visibleDateRange; + /// final HijriDatePickerView _visibleView = args.view; + /// }, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final HijriDatePickerViewChangedCallback onViewChanged; + + /// Called when the new dates or date ranges selected. + /// + /// The dates or ranges that selected when the selection changes available in + /// the [DateRangePickerSelectionChangedArgs]. + /// + /// ``` dart + /// + /// class MyAppState extends State { + /// + /// void _onSelectionChanged(DateRangePickerSelectionChangedArgs args) { + /// if (args.value is HijriDateRange) { + /// final HijriDateTime rangeStartDate = args.value.startDate; + /// final HijriDateTime rangeEndDate = args.value.endDate; + /// } else if (args.value is HijriDateTime) { + /// final HijriDateTime selectedDate = args.value; + /// } else if (args.value is List) { + /// final List selectedDates = args.value; + /// } else { + /// final List selectedRanges = args.value; + /// } + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// title: Text('DatePicker demo'), + /// ), + /// body: SfHijriDateRangePicker( + /// onSelectionChanged: _onSelectionChanged, + /// selectionMode: DateRangePickerSelectionMode.range, + /// initialSelectedRange: HijriDateRange( + /// HijriDateTime.now().subtract(Duration(days: 4)), + /// HijriDateTime.now().add(Duration(days: 3))), + /// ), + /// )); + /// } + ///} + /// + /// ``` + final DateRangePickerSelectionChangedCallback onSelectionChanged; + + @override + Widget build(BuildContext context) { + return _SfDateRangePicker( + key: key, + view: DateRangePickerHelper.getPickerView(view), + selectionMode: selectionMode, + headerHeight: headerHeight, + todayHighlightColor: todayHighlightColor, + backgroundColor: backgroundColor, + initialSelectedDate: initialSelectedDate, + initialSelectedDates: initialSelectedDates, + initialSelectedRange: initialSelectedRange, + initialSelectedRanges: initialSelectedRanges, + toggleDaySelection: toggleDaySelection, + enablePastDates: enablePastDates, + showNavigationArrow: showNavigationArrow, + selectionShape: selectionShape, + navigationDirection: navigationDirection, + controller: controller, + onViewChanged: onViewChanged, + onSelectionChanged: onSelectionChanged, + headerStyle: headerStyle, + yearCellStyle: yearCellStyle, + monthViewSettings: monthViewSettings, + initialDisplayDate: initialDisplayDate, + minDate: minDate, + maxDate: maxDate, + monthCellStyle: monthCellStyle, + allowViewNavigation: allowViewNavigation, + enableMultiView: enableMultiView, + viewSpacing: viewSpacing, + selectionRadius: selectionRadius, + selectionColor: selectionColor, + startRangeSelectionColor: startRangeSelectionColor, + endRangeSelectionColor: endRangeSelectionColor, + rangeSelectionColor: rangeSelectionColor, + selectionTextStyle: selectionTextStyle, + rangeTextStyle: rangeTextStyle, + monthFormat: monthFormat, + cellBuilder: cellBuilder, + isHijri: true, + ); + } +} + +@immutable +class _SfDateRangePicker extends StatefulWidget { + _SfDateRangePicker({ + Key key, + this.view, + this.selectionMode, + this.isHijri = false, + this.headerHeight, + this.todayHighlightColor, + this.backgroundColor, + this.initialSelectedDate, + this.initialSelectedDates, + this.initialSelectedRange, + this.initialSelectedRanges, + this.toggleDaySelection = false, + this.enablePastDates = true, + this.showNavigationArrow = false, + this.selectionShape, + this.navigationDirection, + this.controller, + this.onViewChanged, + this.onSelectionChanged, + this.headerStyle, + this.yearCellStyle, + this.monthViewSettings, + this.initialDisplayDate, + this.minDate, + this.maxDate, + this.monthCellStyle, + bool allowViewNavigation, + bool enableMultiView, + double viewSpacing, + this.selectionRadius, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.selectionTextStyle, + this.rangeTextStyle, + this.monthFormat, + this.cellBuilder, + }) : enableMultiView = enableMultiView ?? false, + viewSpacing = viewSpacing ?? + (enableMultiView != null && enableMultiView ? 20 : 0), + allowViewNavigation = allowViewNavigation ?? true, + super(key: key); + + final DateRangePickerView view; + + final DateRangePickerSelectionMode selectionMode; + + final bool isHijri; + + final DateRangePickerHeaderStyle headerStyle; + + final double headerHeight; + + final Color todayHighlightColor; + + final Color backgroundColor; + + final bool toggleDaySelection; + + final bool allowViewNavigation; + + final bool enableMultiView; + + final double viewSpacing; + + final double selectionRadius; + + final TextStyle selectionTextStyle; + + final TextStyle rangeTextStyle; + + final Color selectionColor; + + final Color startRangeSelectionColor; + + final Color rangeSelectionColor; + + final Color endRangeSelectionColor; + + final dynamic monthViewSettings; + + final DateRangePickerCellBuilder cellBuilder; + + final dynamic yearCellStyle; + + final dynamic monthCellStyle; + + final dynamic initialDisplayDate; + + final dynamic initialSelectedDate; + + final dynamic minDate; + + final dynamic maxDate; + + final bool enablePastDates; + + final List initialSelectedDates; + + final dynamic initialSelectedRange; + + final List initialSelectedRanges; + + final dynamic controller; + + final bool showNavigationArrow; + + final DateRangePickerNavigationDirection navigationDirection; + + final DateRangePickerSelectionShape selectionShape; + + final String monthFormat; + + final dynamic onViewChanged; + + final DateRangePickerSelectionChangedCallback onSelectionChanged; + + @override + _SfDateRangePickerState createState() => _SfDateRangePickerState(); +} + +class _SfDateRangePickerState extends State<_SfDateRangePicker> { + List _currentViewVisibleDates; + dynamic _currentDate, _selectedDate; + double _minWidth, _minHeight; + double _textScaleFactor; + ValueNotifier> _headerVisibleDates; + List _selectedDates; + dynamic _selectedRange; + List _selectedRanges; + GlobalKey _scrollViewKey; + DateRangePickerView _view; + bool _isRtl; + dynamic _controller; + Locale _locale; + SfLocalizations _localizations; + SfDateRangePickerThemeData _datePickerTheme; + + @override + void initState() { + _isRtl = false; + _scrollViewKey = GlobalKey(); + //// Update initial values to controller. + _initPickerController(); + _initNavigation(); + //// Update initial value to state variables. + _updateSelectionValues(); + _view = DateRangePickerHelper.getPickerView(_controller.view); + _updateCurrentVisibleDates(); + _headerVisibleDates = + ValueNotifier>(_currentViewVisibleDates); + _controller.addPropertyChangedListener(_pickerValueChangedListener); + super.initState(); + } + + @override + void didChangeDependencies() { + _textScaleFactor = MediaQuery.of(context).textScaleFactor ?? 1.0; + final TextDirection direction = Directionality.of(context); + // default width value will be device width when the widget placed inside a + // infinity width widget + _minWidth = MediaQuery.of(context).size.width; + // default height for the widget when the widget placed inside a infinity + // height widget + _minHeight = 300; + _locale = Localizations.localeOf(context); + _localizations = SfLocalizations.of(context); + final SfDateRangePickerThemeData pickerTheme = + SfDateRangePickerTheme.of(context); + final ThemeData themeData = Theme.of(context); + _datePickerTheme = pickerTheme.copyWith( + todayTextStyle: pickerTheme.todayTextStyle != null && + pickerTheme.todayTextStyle.color == null + ? pickerTheme.todayTextStyle.copyWith(color: themeData.accentColor) + : pickerTheme.todayTextStyle, + todayCellTextStyle: pickerTheme.todayCellTextStyle != null && + pickerTheme.todayCellTextStyle.color == null + ? pickerTheme.todayCellTextStyle + .copyWith(color: themeData.accentColor) + : pickerTheme.todayCellTextStyle, + selectionColor: pickerTheme.selectionColor ?? themeData.accentColor, + startRangeSelectionColor: + pickerTheme.startRangeSelectionColor ?? themeData.accentColor, + rangeSelectionColor: pickerTheme.rangeSelectionColor ?? + themeData.accentColor.withOpacity(0.1), + endRangeSelectionColor: + pickerTheme.endRangeSelectionColor ?? themeData.accentColor, + todayHighlightColor: + pickerTheme.todayHighlightColor ?? themeData.accentColor); + _isRtl = direction != null && direction == TextDirection.rtl; + super.didChangeDependencies(); + } + + @override + void didUpdateWidget(_SfDateRangePicker oldWidget) { + if (oldWidget.controller != widget.controller) { + oldWidget.controller ?.removePropertyChangedListener(_pickerValueChangedListener); if (widget.controller != null) { _controller.selectedDate = widget.controller.selectedDate; - _controller.selectedDates = _cloneList(widget.controller.selectedDates); + _controller.selectedDates = _getSelectedDates( + DateRangePickerHelper.cloneList(widget.controller.selectedDates)); _controller.selectedRange = widget.controller.selectedRange; - _controller.selectedRanges = - _cloneList(widget.controller.selectedRanges); + _controller.selectedRanges = _getSelectedRanges( + DateRangePickerHelper.cloneList(widget.controller.selectedRanges)); _controller.view = widget.controller.view; _controller.displayDate = widget.controller.displayDate ?? _currentDate; _currentDate = getValidDate( widget.minDate, widget.maxDate, _controller.displayDate); } else { - _initPickerController(); + _initPickerController(); + } + + _controller.view ??= widget.isHijri + ? DateRangePickerHelper.getHijriPickerView(_view) + : DateRangePickerHelper.getPickerView(_view); + _controller.addPropertyChangedListener(_pickerValueChangedListener); + _initNavigation(); + _updateSelectionValues(); + _view = DateRangePickerHelper.getPickerView(_controller.view); + } + + if (oldWidget.monthViewSettings.firstDayOfWeek != + widget.monthViewSettings.firstDayOfWeek) { + _updateCurrentVisibleDates(); + } + + if (oldWidget.selectionMode != widget.selectionMode) { + _updateSelectionValues(); + } + + if (widget.isHijri != oldWidget.isHijri) { + _currentDate = getValidDate(widget.minDate, widget.maxDate, _currentDate); + _updateCurrentVisibleDates(); + } + + if (oldWidget.minDate != widget.minDate || + oldWidget.maxDate != widget.maxDate) { + _currentDate = getValidDate(widget.minDate, widget.maxDate, _currentDate); + } + + if (!widget.isHijri && + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri) != + DateRangePickerHelper.getNumberOfWeeksInView( + oldWidget.monthViewSettings, oldWidget.isHijri)) { + _currentDate = _updateCurrentDate(oldWidget); + _controller.displayDate = _currentDate; + } + + if (oldWidget.controller != widget.controller || + widget.controller == null) { + super.didUpdateWidget(oldWidget); + return; + } + + if (oldWidget.controller.selectedDate != widget.controller.selectedDate) { + _selectedDate = _controller.selectedDate; + } + + if (oldWidget.controller.selectedDates != widget.controller.selectedDates) { + _selectedDates = + DateRangePickerHelper.cloneList(_controller.selectedDates); + } + + if (oldWidget.controller.selectedRange != widget.controller.selectedRange) { + _selectedRange = _controller.selectedRange; + } + + if (oldWidget.controller.selectedRanges != + widget.controller.selectedRanges) { + _selectedRanges = + DateRangePickerHelper.cloneList(_controller.selectedRanges); + } + + if (oldWidget.controller.view != widget.controller.view) { + _view = DateRangePickerHelper.getPickerView(_controller.view); + _currentDate = _updateCurrentDate(oldWidget); + _controller.displayDate = _currentDate; + } + + if (oldWidget.controller.displayDate != widget.controller.displayDate && + widget.controller.displayDate != null) { + _currentDate = + getValidDate(widget.minDate, widget.maxDate, _controller.displayDate); + _controller.displayDate = _currentDate; + } + + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + double top = 0, height; + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + _minWidth = constraints.maxWidth == double.infinity + ? _minWidth + : constraints.maxWidth; + _minHeight = constraints.maxHeight == double.infinity + ? _minHeight + : constraints.maxHeight; + + 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: _addChildren(top, height, _minWidth), + ); + }); + } + + @override + void dispose() { + _controller.removePropertyChangedListener(_pickerValueChangedListener); + super.dispose(); + } + + void _initNavigation() { + _controller.forward = _moveToNextView; + _controller.backward = _moveToPreviousView; + } + + void _initPickerController() { + _controller = widget.controller ?? + (widget.isHijri + ? HijriDatePickerController() + : DateRangePickerController()); + _controller.selectedDate = widget.initialSelectedDate; + _controller.selectedDates = _getSelectedDates( + 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.displayDate = _currentDate; + } + + void _updateSelectionValues() { + _selectedDate = _controller.selectedDate; + _selectedDates = DateRangePickerHelper.cloneList(_controller.selectedDates); + _selectedRange = _controller.selectedRange; + _selectedRanges = + DateRangePickerHelper.cloneList(_controller.selectedRanges); + } + + void _pickerValueChangedListener(String value) { + if (value == 'selectedDate') { + _raiseSelectionChangedCallback(widget, value: _controller.selectedDate); + if (!mounted || isSameDate(_selectedDate, _controller.selectedDate)) { + return; + } + + setState(() { + _selectedDate = _controller.selectedDate; + }); + } else if (value == 'selectedDates') { + _raiseSelectionChangedCallback(widget, value: _controller.selectedDates); + if (!mounted || + DateRangePickerHelper.isDateCollectionEquals( + _selectedDates, _controller.selectedDates)) { + return; + } + + setState(() { + _selectedDates = + DateRangePickerHelper.cloneList(_controller.selectedDates); + }); + } else if (value == 'selectedRange') { + _raiseSelectionChangedCallback(widget, value: _controller.selectedRange); + if (!mounted || + DateRangePickerHelper.isRangeEquals( + _selectedRange, _controller.selectedRange)) { + return; + } + + setState(() { + _selectedRange = _controller.selectedRange; + }); + } else if (value == 'selectedRanges') { + _raiseSelectionChangedCallback(widget, value: _controller.selectedRanges); + if (!mounted || + DateRangePickerHelper.isDateRangesEquals( + _selectedRanges, _controller.selectedRanges)) { + return; + } + + setState(() { + _selectedRanges = + DateRangePickerHelper.cloneList(_controller.selectedRanges); + }); + } else if (value == 'view') { + if (!mounted || + _view == DateRangePickerHelper.getPickerView(_controller.view)) { + return; + } + + setState(() { + _view = DateRangePickerHelper.getPickerView(_controller.view); + _scrollViewKey.currentState._position = 0.0; + _scrollViewKey.currentState._children.clear(); + _scrollViewKey.currentState._updateVisibleDates(); + }); + } else if (value == 'displayDate') { + if (!isSameOrAfterDate(widget.minDate, _controller.displayDate)) { + _controller.displayDate = widget.minDate; + return; + } + + if (!isSameOrBeforeDate(widget.maxDate, _controller.displayDate)) { + _controller.displayDate = widget.maxDate; + return; + } + + //// Restrict the update when current visible dates holds updated display date. + if (isSameDate(_currentDate, _controller.displayDate) || + _checkDateWithInVisibleDates(_controller.displayDate)) { + _currentDate = _controller.displayDate; + return; + } + + if (!mounted) { + return; + } + + setState(() { + _currentDate = _controller.displayDate; + _updateCurrentVisibleDates(); + }); + } + } + + bool _checkDateWithInVisibleDates(dynamic date) { + final DateRangePickerView view = + DateRangePickerHelper.getPickerView(_controller.view); + switch (view) { + case DateRangePickerView.month: + { + if (!widget.isHijri && + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri) != + 6) { + return isDateWithInDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - 1], + date); + } else { + final dynamic currentMonth = _currentViewVisibleDates[ + _currentViewVisibleDates.length ~/ + (widget.enableMultiView ? 4 : 2)]; + return date.month == currentMonth.month && + date.year == currentMonth.year; + } + } + break; + case DateRangePickerView.year: + { + final int currentYear = _currentViewVisibleDates[0].year; + final int year = date.year; + + return currentYear == year; + } + case DateRangePickerView.decade: + { + final int minYear = _currentViewVisibleDates[0].year; + final int maxYear = _currentViewVisibleDates[10].year - 1; + final int year = date.year; + return minYear <= year && maxYear >= year; + } + case DateRangePickerView.century: + { + final int minYear = _currentViewVisibleDates[0].year; + final int maxYear = _currentViewVisibleDates[10].year - 1; + final int year = date.year; + + return minYear <= year && maxYear >= year; + } + } + + return false; + } + + void _updateCurrentVisibleDates() { + switch (_view) { + case DateRangePickerView.month: + { + _currentViewVisibleDates = getVisibleDates( + _currentDate, + null, + widget.monthViewSettings.firstDayOfWeek, + DateRangePickerHelper.getViewDatesCount( + _view, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri), + widget.isHijri), + ); + } + break; + case DateRangePickerView.year: + case DateRangePickerView.decade: + case DateRangePickerView.century: + { + _currentViewVisibleDates = DateRangePickerHelper.getVisibleYearDates( + _currentDate, _view, widget.isHijri); + } + } + } + + dynamic _updateCurrentDate(_SfDateRangePicker oldWidget) { + if (oldWidget.controller == widget.controller && + widget.controller != null && + oldWidget.controller.view == DateRangePickerView.month && + DateRangePickerHelper.getPickerView(_controller.view) != + DateRangePickerView.month) { + return _currentViewVisibleDates[ + _currentViewVisibleDates.length ~/ (widget.enableMultiView ? 4 : 2)]; + } + + return _currentViewVisibleDates[0]; + } + + Widget _addChildren(double top, double height, double width) { + _headerVisibleDates.value = _currentViewVisibleDates; + return Stack(children: [ + Positioned( + top: 0, + right: 0, + left: 0, + height: widget.headerHeight, + child: GestureDetector( + child: Container( + color: widget.headerStyle.backgroundColor ?? + _datePickerTheme.headerBackgroundColor, + child: _PickerHeaderView( + _headerVisibleDates, + widget.headerStyle, + widget.selectionMode, + _view, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri), + widget.showNavigationArrow, + widget.navigationDirection, + widget.monthViewSettings.enableSwipeSelection, + widget.minDate, + widget.maxDate, + widget.monthFormat, + _datePickerTheme, + _locale, + width, + widget.headerHeight, + widget.allowViewNavigation, + _controller.backward, + _controller.forward, + widget.enableMultiView, + widget.viewSpacing, + widget.selectionColor ?? _datePickerTheme.selectionColor, + _isRtl, + _textScaleFactor, + widget.isHijri, + _localizations), + height: widget.headerHeight, + ), + onTapUp: (TapUpDetails details) { + _updateCalendarTapCallbackForHeader(); + }, + ), + ), + _getViewHeaderView(), + Positioned( + top: top, + left: 0, + right: 0, + height: height, + child: _PickerScrollView( + widget, + _controller, + width, + height, + _isRtl, + _datePickerTheme, + _locale, + _textScaleFactor, + getPickerStateValues: (PickerStateArgs details) { + _getPickerStateValues(details); + }, + updatePickerStateValues: (PickerStateArgs details) { + _updatePickerStateValues(details); + }, + key: _scrollViewKey, + ), + ), + ]); + } + + Widget _getViewHeaderView() { + if (_view == DateRangePickerView.month && + widget.navigationDirection == + DateRangePickerNavigationDirection.vertical) { + final Color todayTextColor = + widget.monthCellStyle.todayTextStyle != null && + widget.monthCellStyle.todayTextStyle.color != null + ? widget.monthCellStyle.todayTextStyle.color + : (widget.todayHighlightColor != null && + widget.todayHighlightColor != Colors.transparent + ? widget.todayHighlightColor + : _datePickerTheme.todayHighlightColor); + return Positioned( + left: 0, + top: widget.headerHeight, + right: 0, + height: widget.monthViewSettings.viewHeaderHeight, + child: Container( + color: widget.monthViewSettings.viewHeaderStyle.backgroundColor ?? + _datePickerTheme.viewHeaderBackgroundColor, + child: RepaintBoundary( + child: CustomPaint( + painter: _PickerViewHeaderPainter( + _currentViewVisibleDates, + widget.monthViewSettings.viewHeaderStyle, + widget.monthViewSettings.viewHeaderHeight, + widget.monthViewSettings, + _datePickerTheme, + _locale, + _isRtl, + widget.monthCellStyle, + widget.enableMultiView, + widget.viewSpacing, + todayTextColor, + _textScaleFactor, + widget.isHijri, + widget.navigationDirection), + ), + ), + ), + ); + } + + return Positioned(left: 0, top: 0, right: 0, height: 0, child: Container()); + } + + void _moveToNextView() { + if (!DateRangePickerHelper.canMoveToNextView( + _view, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri), + widget.maxDate, + _currentViewVisibleDates, + widget.enableMultiView, + widget.isHijri)) { + return; + } + + _isRtl + ? _scrollViewKey.currentState._moveToPreviousViewWithAnimation() + : _scrollViewKey.currentState._moveToNextViewWithAnimation(); + } + + void _moveToPreviousView() { + if (!DateRangePickerHelper.canMoveToPreviousView( + _view, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri), + widget.minDate, + _currentViewVisibleDates, + widget.enableMultiView, + widget.isHijri)) { + return; + } + + _isRtl + ? _scrollViewKey.currentState._moveToNextViewWithAnimation() + : _scrollViewKey.currentState._moveToPreviousViewWithAnimation(); + } + + void _getPickerStateValues(PickerStateArgs details) { + details.currentDate = _currentDate; + details.selectedDate = _selectedDate; + details.selectedDates = _selectedDates; + details.selectedRange = _selectedRange; + details.selectedRanges = _selectedRanges; + details.view = DateRangePickerHelper.getPickerView(_view); + } + + void _updatePickerStateValues(PickerStateArgs details) { + if (details.currentDate != null) { + if (!isSameOrAfterDate(widget.minDate, details.currentDate)) { + details.currentDate = widget.minDate; + } + + if (!isSameOrBeforeDate(widget.maxDate, details.currentDate)) { + details.currentDate = widget.maxDate; + } + + _currentDate = details.currentDate; + _controller.displayDate = _currentDate; + } + + if (details.currentViewVisibleDates != null && + _currentViewVisibleDates != details.currentViewVisibleDates) { + _currentViewVisibleDates = details.currentViewVisibleDates; + _headerVisibleDates.value = _currentViewVisibleDates; + final DateRangePickerView view = + DateRangePickerHelper.getPickerView(_controller.view); + dynamic visibleDateRange; + switch (view) { + case DateRangePickerView.month: + { + if (widget.isHijri || + (!DateRangePickerHelper.canShowLeadingAndTrailingDates( + widget.monthViewSettings, widget.isHijri) && + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri) == + 6)) { + final dynamic visibleDate = _currentViewVisibleDates[ + _currentViewVisibleDates.length ~/ + (widget.enableMultiView ? 4 : 2)]; + if (widget.isHijri) { + visibleDateRange = HijriDateRange( + DateRangePickerHelper.getMonthStartDate( + visibleDate, widget.isHijri), + widget.enableMultiView + ? DateRangePickerHelper.getMonthEndDate( + DateRangePickerHelper.getNextViewStartDate( + DateRangePickerHelper.getPickerView( + _controller.view), + 6, + visibleDate, + _isRtl, + widget.isHijri)) + : DateRangePickerHelper.getMonthEndDate(visibleDate)); + } else { + visibleDateRange = PickerDateRange( + DateRangePickerHelper.getMonthStartDate( + visibleDate, widget.isHijri), + widget.enableMultiView + ? DateRangePickerHelper.getMonthEndDate( + DateRangePickerHelper.getNextViewStartDate( + DateRangePickerHelper.getPickerView( + _controller.view), + 6, + visibleDate, + _isRtl, + widget.isHijri)) + : DateRangePickerHelper.getMonthEndDate(visibleDate)); + } + _raisePickerViewChangedCallback(widget, + visibleDateRange: visibleDateRange, view: _controller.view); + } else { + if (widget.isHijri) { + visibleDateRange = HijriDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[ + _currentViewVisibleDates.length - 1]); + } else { + visibleDateRange = PickerDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[ + _currentViewVisibleDates.length - 1]); + } + _raisePickerViewChangedCallback(widget, + visibleDateRange: visibleDateRange, view: _controller.view); + } + } + break; + case DateRangePickerView.year: + case DateRangePickerView.decade: + case DateRangePickerView.century: + { + if (widget.isHijri) { + visibleDateRange = HijriDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[ + _currentViewVisibleDates.length - 1]); + } else { + visibleDateRange = PickerDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[ + _currentViewVisibleDates.length - 1]); + } + _raisePickerViewChangedCallback(widget, + visibleDateRange: visibleDateRange, view: _controller.view); + } + } + } + + if (details.view != null && _view != details.view) { + _controller.view = widget.isHijri + ? DateRangePickerHelper.getHijriPickerView(details.view) + : DateRangePickerHelper.getPickerView(details.view); + } + + if (_view == DateRangePickerView.month || !widget.allowViewNavigation) { + switch (widget.selectionMode) { + case DateRangePickerSelectionMode.single: + { + _selectedDate = details.selectedDate; + _controller.selectedDate = _selectedDate; + } + break; + case DateRangePickerSelectionMode.multiple: + { + _selectedDates = details.selectedDates; + _controller.selectedDates = _getSelectedDates(_selectedDates); + } + break; + case DateRangePickerSelectionMode.range: + { + _selectedRange = details.selectedRange; + _controller.selectedRange = _selectedRange; + } + break; + case DateRangePickerSelectionMode.multiRange: + { + _selectedRanges = details.selectedRanges; + _controller.selectedRanges = _getSelectedRanges(_selectedRanges); + } + } + } + } + + /// returns the selected ranges in the required type list. + List _getSelectedRanges(List ranges) { + if (ranges == null) { + return ranges; + } + + List selectedRanges; + if (widget.isHijri) { + selectedRanges = []; + } else { + selectedRanges = []; + } + + for (int i = 0; i < ranges.length; i++) { + selectedRanges.add(ranges[i]); + } + + return selectedRanges; + } + + /// returns the selected dates in the required type list + List _getSelectedDates(List dates) { + if (dates == null) { + return dates; + } + + List selectedDates; + if (widget.isHijri) { + selectedDates = []; + } else { + selectedDates = []; + } + + for (int i = 0; i < dates.length; i++) { + selectedDates.add(dates[i]); + } + + return selectedDates; + } + + // method to update the picker tapped call back for the header view + void _updateCalendarTapCallbackForHeader() { + if (_view == DateRangePickerView.century || !widget.allowViewNavigation) { + return; + } + + if (_view == DateRangePickerView.month) { + _controller.view = widget.isHijri + ? DateRangePickerHelper.getHijriPickerView(DateRangePickerView.year) + : DateRangePickerHelper.getPickerView(DateRangePickerView.year); + } else { + if (_view == DateRangePickerView.year) { + _controller.view = widget.isHijri + ? DateRangePickerHelper.getHijriPickerView( + DateRangePickerView.decade) + : DateRangePickerHelper.getPickerView(DateRangePickerView.decade); + } else if (_view == DateRangePickerView.decade) { + _controller.view = widget.isHijri + ? DateRangePickerHelper.getHijriPickerView( + DateRangePickerView.century) + : DateRangePickerHelper.getPickerView(DateRangePickerView.century); + } + } + } +} + +/// Holds the picker header text, navigation arrow and handle its interactions. +@immutable +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.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; + + /// Defines the selection mode of [SfDateRangePicker]. + final DateRangePickerSelectionMode selectionMode; + + /// Defines the header style of [SfDateRangePicker]. + final DateRangePickerHeaderStyle headerStyle; + + /// Holds the current picker view of picker. + final DateRangePickerView view; + + /// Defines the number of week in [SfDateRangePicker] month view. + final int numberOfWeeksInView; + + /// Decides the navigation arrow will shown or not. + final bool showNavigationArrow; + + /// Defines the navigation direction of [SfDateRangePicker]. + final DateRangePickerNavigationDirection navigationDirection; + + /// The minimum date as much as the [SfDateRangePicker] will navigate. + final dynamic minDate; + + /// The maximum date as much as the [SfDateRangePicker] will navigate. + final dynamic maxDate; + + /// Defines the month format used on header view text. + final String monthFormat; + + /// Decides the swipe selection enabled or not. + final bool enableSwipeSelection; + + /// Decides the view navigation allowed or not. + final bool allowViewNavigation; + + /// Holds the theme data for date range picker. + final SfDateRangePickerThemeData datePickerTheme; + + /// Defines the locale details of date range picker. + final Locale locale; + + /// Holds the visible dates for the current picker view. + final ValueNotifier> visibleDates; + + /// Holds the previous navigation call back for date range picker. + final VoidCallback previousNavigationCallback; + + /// Holds the next navigation call back for date range picker. + final VoidCallback nextNavigationCallback; + + /// Holds the picker view width used on widget positioning. + final double width; + + /// Holds the picker view height used on widget positioning. + final double height; + + /// Used to identify the widget direction is RTL. + final bool isRtl; + + /// Holds the header hover color. + final Color hoverColor; + + /// Decides to show the multi view of picker or not. + final bool enableMultiView; + + /// Specifies the space between the multi month views. + final double multiViewSpacing; + + /// Specifies the localization details + final SfLocalizations localizations; + + /// Specifies the picker mode for [SfDateRangePicker]. + final bool isHijri; + + @override + _PickerHeaderViewState createState() => _PickerHeaderViewState(); +} + +class _PickerHeaderViewState extends State<_PickerHeaderView> { + bool _hovering; + + @override + void initState() { + _hovering = false; + _addListener(); + super.initState(); + } + + @override + void didUpdateWidget(_PickerHeaderView oldWidget) { + widget.visibleDates.removeListener(_listener); + _addListener(); + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + final bool isMobilePlatform = + DateRangePickerHelper.isMobileLayout(Theme.of(context).platform); + double arrowWidth = 0; + double headerWidth = widget.width; + if (widget.showNavigationArrow || + ((widget.view == DateRangePickerView.month || + !widget.allowViewNavigation) && + widget.enableSwipeSelection && + (widget.selectionMode == DateRangePickerSelectionMode.range || + widget.selectionMode == + DateRangePickerSelectionMode.multiRange))) { + arrowWidth = widget.width / 6; + arrowWidth = arrowWidth > 50 ? 50 : arrowWidth; + headerWidth = widget.width - (arrowWidth * 2); + } + + Color arrowColor = widget.headerStyle.textStyle != null + ? widget.headerStyle.textStyle.color + : (widget.datePickerTheme.headerTextStyle.color); + arrowColor = arrowColor.withOpacity(arrowColor.opacity * 0.6); + Color prevArrowColor = arrowColor; + Color nextArrowColor = arrowColor; + final List dates = widget.visibleDates.value; + if (!DateRangePickerHelper.canMoveToNextView( + widget.view, + widget.numberOfWeeksInView, + widget.maxDate, + dates, + widget.enableMultiView, + widget.isHijri)) { + nextArrowColor = nextArrowColor.withOpacity(arrowColor.opacity * 0.5); + } + + if (!DateRangePickerHelper.canMoveToPreviousView( + widget.view, + widget.numberOfWeeksInView, + widget.minDate, + dates, + widget.enableMultiView, + widget.isHijri)) { + prevArrowColor = prevArrowColor.withOpacity(arrowColor.opacity * 0.5); + } + + final Widget headerText = _getHeaderText(headerWidth, isMobilePlatform); + + double arrowSize = widget.height * 0.5; + arrowSize = arrowSize > 25 ? 25 : arrowSize; + arrowSize = arrowSize * widget.textScaleFactor; + final Container leftArrow = + _getLeftArrow(arrowWidth, arrowColor, prevArrowColor, arrowSize); + + final Container rightArrow = + _getRightArrow(arrowWidth, arrowColor, nextArrowColor, arrowSize); + + if (widget.headerStyle.textAlign == TextAlign.left || + widget.headerStyle.textAlign == TextAlign.start) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.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, + ]); + } else { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + leftArrow, + headerText, + rightArrow, + ]); + } + } + + @override + void dispose() { + widget.visibleDates.removeListener(_listener); + super.dispose(); + } + + void _listener() { + if (!mounted) { + return; + } + + if (widget.showNavigationArrow || + ((widget.view == DateRangePickerView.month || + !widget.allowViewNavigation) && + widget.enableSwipeSelection && + (widget.selectionMode == DateRangePickerSelectionMode.range || + widget.selectionMode == + DateRangePickerSelectionMode.multiRange))) { + setState(() { + /*Updates the header when visible dates changes */ + }); + } + } + + void _addListener() { + 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; + } + + 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( + // 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), + size: Size(headerWidth, widget.height), + ))); + } + + Widget _getLeftArrow(double arrowWidth, Color arrowColor, + Color prevArrowColor, double arrowSize) { + return Container( + alignment: Alignment.center, + color: widget.headerStyle.backgroundColor ?? + widget.datePickerTheme.headerBackgroundColor, + width: arrowWidth, + padding: const EdgeInsets.all(0), + child: FlatButton( + //// set splash color as transparent when arrow reaches min date(disabled) + splashColor: prevArrowColor != arrowColor ? Colors.transparent : null, + hoverColor: prevArrowColor != arrowColor ? Colors.transparent : null, + highlightColor: + prevArrowColor != arrowColor ? Colors.transparent : null, + color: widget.headerStyle.backgroundColor ?? + widget.datePickerTheme.headerBackgroundColor, + onPressed: widget.previousNavigationCallback, + padding: const EdgeInsets.all(0), + child: Semantics( + label: 'Backward', + child: Icon( + widget.navigationDirection == + DateRangePickerNavigationDirection.horizontal + ? Icons.chevron_left + : Icons.keyboard_arrow_up, + color: prevArrowColor, + size: arrowSize, + ), + ), + ), + ); + } + + Widget _getRightArrow(double arrowWidth, Color arrowColor, + Color nextArrowColor, double arrowSize) { + return Container( + alignment: Alignment.center, + color: widget.headerStyle.backgroundColor ?? + widget.datePickerTheme.headerBackgroundColor, + width: arrowWidth, + padding: const EdgeInsets.all(0), + child: FlatButton( + //// set splash color as transparent when arrow reaches max date(disabled) + splashColor: nextArrowColor != arrowColor ? Colors.transparent : null, + hoverColor: nextArrowColor != arrowColor ? Colors.transparent : null, + highlightColor: + nextArrowColor != arrowColor ? Colors.transparent : null, + color: widget.headerStyle.backgroundColor ?? + widget.datePickerTheme.headerBackgroundColor, + onPressed: widget.nextNavigationCallback, + padding: const EdgeInsets.all(0), + child: Semantics( + label: 'Forward', + child: Icon( + widget.navigationDirection == + DateRangePickerNavigationDirection.horizontal + ? Icons.chevron_right + : Icons.keyboard_arrow_down, + color: nextArrowColor, + size: arrowSize, + ), + ), + ), + ); + } +} + +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); + + final DateRangePickerHeaderStyle headerStyle; + final DateRangePickerView view; + final int numberOfWeeksInView; + final SfDateRangePickerThemeData datePickerTheme; + final bool isRtl; + final String monthFormat; + final bool hovering; + final bool enableMultiView; + final double multiViewSpacing; + final Color hoverColor; + final Locale locale; + final double textScaleFactor; + final bool isHijri; + final SfLocalizations localizations; + final DateRangePickerNavigationDirection navigationDirection; + ValueNotifier> visibleDates; + String _headerText; + TextPainter _textPainter; + + @override + void paint(Canvas canvas, Size size) { + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + double xPosition = 0; + _textPainter = _textPainter ?? TextPainter(); + _textPainter.textDirection = TextDirection.ltr; + _textPainter.textWidthBasis = TextWidthBasis.longestLine; + _textPainter.textScaleFactor = textScaleFactor; + _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; + for (int j = 0; j < count; j++) { + final int currentViewIndex = + isRtl ? DateRangePickerHelper.getRtlIndex(count, j) : j; + xPosition = (currentViewIndex * width) + 10; + final String text = _getHeaderText(j); + _headerText += j == 1 ? ' ' + text : text; + TextStyle style = + headerStyle.textStyle ?? datePickerTheme.headerTextStyle; + if (hovering) { + style = style.copyWith(color: hoverColor); + } + + final TextSpan span = TextSpan(text: text, style: style); + _textPainter.text = span; + + if (headerStyle.textAlign == TextAlign.justify) { + _textPainter.textAlign = headerStyle.textAlign; + } + + double textWidth = ((currentViewIndex + 1) * width) - xPosition; + textWidth = textWidth > 0 ? textWidth : 0; + _textPainter.layout(minWidth: textWidth, maxWidth: textWidth); + + if (headerStyle.textAlign == TextAlign.center) { + xPosition = (currentViewIndex * width) + + (currentViewIndex * multiViewSpacing) + + (width / 2) - + (_textPainter.width / 2); + } else if ((!isRtl && + (headerStyle.textAlign == TextAlign.right || + headerStyle.textAlign == TextAlign.end)) || + (isRtl && + (headerStyle.textAlign == TextAlign.left || + headerStyle.textAlign == TextAlign.start))) { + xPosition = + ((currentViewIndex + 1) * width) - _textPainter.width - xPosition; + } + _textPainter.paint( + canvas, Offset(xPosition, size.height / 2 - _textPainter.height / 2)); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + final _PickerHeaderPainter oldWidget = oldDelegate; + return oldWidget.headerStyle != headerStyle || + oldWidget.isRtl != isRtl || + oldWidget.numberOfWeeksInView != numberOfWeeksInView || + oldWidget.locale != locale || + oldWidget.datePickerTheme != datePickerTheme || + oldWidget.textScaleFactor != textScaleFactor || + oldWidget.hovering != hovering || + oldWidget.hoverColor != hoverColor; + } + + String _getMonthHeaderText(int startIndex, int endIndex, List dates, + int middleIndex, int datesCount) { + if ((!isHijri && numberOfWeeksInView != 6) && + dates[startIndex].month != dates[endIndex].month) { + final String monthTextFormat = + monthFormat == null || monthFormat.isEmpty ? 'MMM' : monthFormat; + int endIndex = dates.length - 1; + if (enableMultiView && headerStyle.textAlign == TextAlign.center) { + endIndex = endIndex; + } + + final String startText = DateFormat(monthTextFormat, locale.toString()) + .format(dates[startIndex]) + .toString() + + ' ' + + dates[startIndex].year.toString(); + final String endText = DateFormat(monthTextFormat, locale.toString()) + .format(dates[endIndex]) + .toString() + + ' ' + + dates[endIndex].year.toString(); + if (startText == endText) { + return startText; + } + + return startText + ' - ' + endText; + } else { + final String monthTextFormat = monthFormat == null || monthFormat.isEmpty + ? enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.vertical + ? 'MMM' + : 'MMMM' + : monthFormat; + String text; + dynamic middleDate = dates[middleIndex]; + if (isHijri) { + text = DateRangePickerHelper.getHijriMonthText( + middleDate, localizations, monthTextFormat) + + ' ' + + middleDate.year.toString(); + } else { + text = DateFormat(monthTextFormat, locale.toString()) + .format(middleDate) + .toString() + + ' ' + + middleDate.year.toString(); + } + + /// To restrict the double header when the number of weeks in view given + /// and the dates were the same month. + if (enableMultiView && + navigationDirection == DateRangePickerNavigationDirection.vertical && + numberOfWeeksInView != 6 && + dates[startIndex].month == dates[endIndex].month) { + return text; + } + + if ((enableMultiView && headerStyle.textAlign != TextAlign.center) || + (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.vertical)) { + middleDate = dates[datesCount + middleIndex]; + if (isHijri) { + return text + + '_' + + DateRangePickerHelper.getHijriMonthText( + middleDate, localizations, monthTextFormat) + + ' ' + + middleDate.year.toString(); + } else { + return text + + ' - ' + + DateFormat(monthTextFormat, locale.toString()) + .format(middleDate) + .toString() + + ' ' + + middleDate.year.toString(); + } + } + + return text; + } + } + + String _getHeaderText(int index) { + final List dates = visibleDates.value; + final int count = enableMultiView ? 2 : 1; + final int datesCount = dates.length ~/ count; + final int startIndex = index * datesCount; + final int endIndex = ((index + 1) * datesCount) - 1; + final int middleIndex = startIndex + (datesCount ~/ 2); + switch (view) { + case DateRangePickerView.month: + { + return _getMonthHeaderText( + startIndex, endIndex, dates, middleIndex, datesCount); + } + break; + case DateRangePickerView.year: + { + final dynamic date = dates[middleIndex]; + if ((enableMultiView && headerStyle.textAlign != TextAlign.center) || + (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.vertical)) { + return date.year.toString() + + ' - ' + + dates[datesCount + middleIndex].year.toString(); + } + + return date.year.toString(); + } + break; + case DateRangePickerView.decade: + { + final int year = (dates[middleIndex].year ~/ 10) * 10; + if ((enableMultiView && headerStyle.textAlign != TextAlign.center) || + (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.vertical)) { + return year.toString() + + ' - ' + + (((dates[datesCount + middleIndex].year ~/ 10) * 10) + 9) + .toString(); + } + + return year.toString() + ' - ' + (year + 9).toString(); + } + break; + case DateRangePickerView.century: + { + final int year = (dates[middleIndex].year ~/ 100) * 100; + if ((enableMultiView && headerStyle.textAlign != TextAlign.center) || + (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.vertical)) { + return year.toString() + + ' - ' + + (((dates[datesCount + middleIndex].year ~/ 100) * 100) + 99) + .toString(); + } + + return year.toString() + ' - ' + (year + 99).toString(); + } + } + + return ''; + } + + /// overrides this property to build the semantics information which uses to + /// return the required information for accessibility, need to return the list + /// of custom painter semantics which contains the rect area and the semantics + /// properties for accessibility + @override + SemanticsBuilderCallback get semanticsBuilder { + return (Size size) { + final Rect rect = Offset.zero & size; + return [ + CustomPainterSemantics( + rect: rect, + properties: SemanticsProperties( + label: _headerText != null ? _headerText.replaceAll('-', 'to') : '', + textDirection: TextDirection.ltr, + ), + ), + ]; + }; + } + + @override + bool shouldRebuildSemantics(CustomPainter oldDelegate) { + return true; + } +} + +/// Holds the view header cells of the date range picker. +class _PickerViewHeaderPainter extends CustomPainter { + /// Constructor to create picker view header view instance. + _PickerViewHeaderPainter( + this.visibleDates, + 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); + + /// Defines the view header style. + final DateRangePickerViewHeaderStyle viewHeaderStyle; + + /// Defines the month view settings. + final dynamic monthViewSettings; + + /// Holds the visible dates for the month view. + final List visibleDates; + + /// Defines the height of the view header height. + final double viewHeaderHeight; + + /// Defines the month cell style. + final dynamic monthCellStyle; + + /// Defines the locale details of date range picker. + final Locale locale; + + /// Used to identify the widget direction is RTL. + final bool isRtl; + + /// Defines the today cell highlight color. + final Color todayHighlightColor; + + /// Decides to show the multi view of picker or not. + final bool enableMultiView; + + /// Specifies the space between the multi month views. + final double multiViewSpacing; + + /// Holds the theme data for date range picker. + final SfDateRangePickerThemeData datePickerTheme; + + /// Specifies the picker type for [SfDateRangePicker]. + final bool isHijri; + + /// Defines the text scale factor of [SfDateRangePicker]. + final double textScaleFactor; + + /// Defines the navigation direction for [SfDateRangePicker]. + final DateRangePickerNavigationDirection navigationDirection; + TextPainter _textPainter; + + @override + void paint(Canvas canvas, Size size) { + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + double width = size.width / DateTime.daysPerWeek; + if (enableMultiView && + navigationDirection == DateRangePickerNavigationDirection.horizontal) { + width = (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 dynamic today = DateRangePickerHelper.getToday(isHijri); + TextStyle dayTextStyle = viewHeaderDayStyle; + double xPosition = 0; + double yPosition = 0; + final int count = (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.horizontal) + ? 2 + : 1; + final int datesCount = (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.horizontal) + ? visibleDates.length ~/ 2 + : visibleDates.length; + for (int j = 0; j < count; j++) { + final int currentViewIndex = + isRtl ? DateRangePickerHelper.getRtlIndex(count, j) : j; + dynamic currentDate; + final int month = + visibleDates[(currentViewIndex * datesCount) + (datesCount ~/ 2)] + .month; + final int year = + visibleDates[(currentViewIndex * datesCount) + (datesCount ~/ 2)] + .year; + final int currentMonth = today.month; + final int currentYear = today.year; + + final int numberOfWeeksInView = + DateRangePickerHelper.getNumberOfWeeksInView( + monthViewSettings, isHijri); + final bool hasToday = numberOfWeeksInView > 0 && numberOfWeeksInView < 6 + ? true + : (month == currentMonth && year == currentYear) + ? true + : false; + for (int i = 0; i < DateTime.daysPerWeek; i++) { + int index = isRtl + ? DateRangePickerHelper.getRtlIndex(DateTime.daysPerWeek, i) + : i; + index = index + (currentViewIndex * datesCount); + currentDate = visibleDates[index]; + String dayText = + DateFormat(monthViewSettings.dayFormat, locale.toString()) + .format(isHijri ? currentDate.toDateTime() : currentDate) + .toString() + .toUpperCase(); + dayText = _updateViewHeaderFormat(dayText); + + if (hasToday && + currentDate.weekday == today.weekday && + isDateWithInDateRange( + visibleDates[(currentViewIndex * datesCount)], + visibleDates[((currentViewIndex + 1) * datesCount) - 1], + today)) { + final Color textColor = monthCellStyle.todayTextStyle != null && + monthCellStyle.todayTextStyle.color != null + ? monthCellStyle.todayTextStyle.color + : todayHighlightColor ?? datePickerTheme.todayHighlightColor; + dayTextStyle = viewHeaderDayStyle.copyWith(color: textColor); + } else { + dayTextStyle = viewHeaderDayStyle; + } + + final TextSpan dayTextSpan = TextSpan( + text: dayText, + style: dayTextStyle, + ); + + _textPainter = _textPainter ?? + TextPainter( + textDirection: TextDirection.ltr, + textAlign: TextAlign.left, + textScaleFactor: textScaleFactor, + textWidthBasis: TextWidthBasis.longestLine); + _textPainter.text = dayTextSpan; + _textPainter.layout(minWidth: width, maxWidth: width); + yPosition = (viewHeaderHeight - _textPainter.height) / 2; + _textPainter.paint( + canvas, + Offset( + xPosition + (width / 2 - _textPainter.width / 2), yPosition)); + xPosition += width; + } + + xPosition += multiViewSpacing; + } + } + + String _updateViewHeaderFormat(String dayText) { + //// EE format value shows the week days as S, M, T, W, T, F, S. + /// For other languages showing the first letter of the weekday turns into + /// wrong meaning, hence we have shown the first letter of weekday when the + /// date format set as default and the locale set as English. + /// + /// Eg: In chinese the first letter or `Sunday` represents `Weekday`, hence + /// to avoid this added this condition based on locale. + if (monthViewSettings.dayFormat == 'EE' && + (locale == null || locale.languageCode == 'en')) { + dayText = dayText[0]; + } + + return dayText; + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + final _PickerViewHeaderPainter oldWidget = oldDelegate; + return oldWidget.visibleDates != visibleDates || + oldWidget.viewHeaderStyle != viewHeaderStyle || + oldWidget.viewHeaderHeight != viewHeaderHeight || + oldWidget.todayHighlightColor != todayHighlightColor || + oldWidget.monthViewSettings != monthViewSettings || + oldWidget.datePickerTheme != datePickerTheme || + oldWidget.isRtl != isRtl || + oldWidget.locale != locale || + oldWidget.textScaleFactor != textScaleFactor || + oldWidget.isHijri != isHijri; + } + + List _getSemanticsBuilder(Size size) { + final List semanticsBuilder = + []; + double left, cellWidth; + cellWidth = size.width / DateTime.daysPerWeek; + int count = 1; + int datesCount = visibleDates.length; + if (enableMultiView && + navigationDirection == DateRangePickerNavigationDirection.horizontal) { + cellWidth = (size.width - multiViewSpacing) / 14; + count = 2; + datesCount = visibleDates.length ~/ 2; + } + + left = isRtl ? size.width - cellWidth : 0; + 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]) + .toString() + .toUpperCase(), + textDirection: TextDirection.ltr, + ), + )); + if (isRtl) { + left -= cellWidth; + } else { + left += cellWidth; + } + } + + if (isRtl) { + left -= multiViewSpacing; + } else { + left += multiViewSpacing; + } + } + + return semanticsBuilder; + } + + /// overrides this property to build the semantics information which uses to + /// return the required information for accessibility, need to return the list + /// of custom painter semantics which contains the rect area and the semantics + /// properties for accessibility + @override + SemanticsBuilderCallback get semanticsBuilder { + return (Size size) { + return _getSemanticsBuilder(size); + }; + } + + @override + bool shouldRebuildSemantics(CustomPainter oldDelegate) { + final _PickerViewHeaderPainter oldWidget = oldDelegate; + return oldWidget.visibleDates != visibleDates; + } +} + +/// Holds the picker views and handles the scrolling or swiping +/// related operations. +@immutable +class _PickerScrollView extends StatefulWidget { + /// Constructor to create the picker scroll view instance. + const _PickerScrollView(this.picker, this.controller, this.width, this.height, + this.isRtl, this.datePickerTheme, this.locale, this.textScaleFactor, + {Key key, this.getPickerStateValues, this.updatePickerStateValues}) + : super(key: key); + + /// Holds the picker instance to access the picker details. + final _SfDateRangePicker picker; + + /// Holds the picker scroll view width. + final double width; + + /// Holds the picker scroll view height. + final double height; + + /// Used to identify the widget direction is RTL. + final bool isRtl; + + /// Used to get the picker values from date picker state. + final UpdatePickerState getPickerStateValues; + + /// Used to update the picker values to date picker state. + final UpdatePickerState updatePickerStateValues; + + /// Holds the controller details used on its state + final dynamic controller; + + /// Holds the theme data for date range picker. + final SfDateRangePickerThemeData datePickerTheme; + + /// Holds the picker locale. + final Locale locale; + + /// Defines the text scale factor of [SfDateRangePicker]. + final double textScaleFactor; + + @override + _PickerScrollViewState createState() => _PickerScrollViewState(); +} + +/// Handle the picker scroll view children position and it interaction. +class _PickerScrollViewState extends State<_PickerScrollView> + with TickerProviderStateMixin { + // three views to arrange the view in vertical/horizontal direction and handle the swiping + _PickerView _currentView, _nextView, _previousView; + + // the three children which to be added into the layout + List<_PickerView> _children; + + // holds the index of the current displaying view + int _currentChildIndex; + + // _scrollStartPosition contains the touch movement starting position + double _scrollStartPosition; + + // _position contains distance that the view swiped + double _position; + + // animation controller to control the animation + AnimationController _animationController; + + // animation handled for the view swiping + Animation _animation; + + // tween animation to handle the animation + Tween _tween; + + // three visible dates for the three views, the dates will updated based on + // the swiping in the swipe end _currentViewVisibleDates which stores the + // visible dates of the current displaying view + List _visibleDates, + _previousViewVisibleDates, + _nextViewVisibleDates, + _currentViewVisibleDates; + + /// keys maintained to access the data and methods from the picker view + /// class. + GlobalKey<_PickerViewState> _previousViewKey, _currentViewKey, _nextViewKey; + + PickerStateArgs _pickerStateDetails; + FocusNode _focusNode; + + @override + void initState() { + _previousViewKey = GlobalKey<_PickerViewState>(); + _currentViewKey = GlobalKey<_PickerViewState>(); + _nextViewKey = GlobalKey<_PickerViewState>(); + _focusNode = FocusNode(); + _pickerStateDetails = PickerStateArgs(); + _currentChildIndex = 1; + _updateVisibleDates(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 250), + vsync: this, + animationBehavior: AnimationBehavior.normal); + _tween = Tween(begin: 0.0, end: 0.1); + _animation = _tween.animate(_animationController) + ..addListener(_animationListener); + + super.initState(); + } + + @override + void didUpdateWidget(_PickerScrollView oldWidget) { + if (widget.picker.navigationDirection != + oldWidget.picker.navigationDirection || + widget.width != oldWidget.width || + widget.picker.cellBuilder != oldWidget.picker.cellBuilder || + oldWidget.datePickerTheme != widget.datePickerTheme || + widget.picker.viewSpacing != oldWidget.picker.viewSpacing || + widget.picker.selectionMode != oldWidget.picker.selectionMode || + widget.height != oldWidget.height) { + _position = 0; + _children.clear(); + } + + if (oldWidget.textScaleFactor != widget.textScaleFactor || + oldWidget.picker.isHijri != widget.picker.isHijri) { + _position = 0; + _children.clear(); + } + + if (oldWidget.picker.controller != widget.picker.controller) { + _position = 0; + _children.clear(); + _updateVisibleDates(); + } + + if (widget.isRtl != oldWidget.isRtl || + widget.picker.enableMultiView != oldWidget.picker.enableMultiView) { + _position = 0; + _children.clear(); + _updateVisibleDates(); + } + + _updateSettings(oldWidget); + + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + + if (pickerView == DateRangePickerView.year && + widget.picker.monthFormat != oldWidget.picker.monthFormat) { + _position = 0; + _children.clear(); + } + + if (pickerView != DateRangePickerView.month && + widget.picker.yearCellStyle != oldWidget.picker.yearCellStyle) { + _position = 0; + _children.clear(); + } + + if (widget.picker.minDate != oldWidget.picker.minDate || + widget.picker.maxDate != oldWidget.picker.maxDate) { + final dynamic previousVisibleDate = _pickerStateDetails.currentDate; + widget.getPickerStateValues(_pickerStateDetails); + if (!isSameDate(_pickerStateDetails.currentDate, previousVisibleDate)) { + _updateVisibleDates(); + } + + _position = 0; + _children.clear(); + } + + if (widget.picker.enablePastDates != oldWidget.picker.enablePastDates) { + _position = 0; + _children.clear(); + } + + if (pickerView == DateRangePickerView.month && + (oldWidget.picker.monthViewSettings.viewHeaderStyle != + widget.picker.monthViewSettings.viewHeaderStyle || + oldWidget.picker.monthViewSettings.viewHeaderHeight != + widget.picker.monthViewSettings.viewHeaderHeight || + DateRangePickerHelper.canShowLeadingAndTrailingDates( + widget.picker.monthViewSettings, widget.picker.isHijri) != + DateRangePickerHelper.canShowLeadingAndTrailingDates( + oldWidget.picker.monthViewSettings, + oldWidget.picker.isHijri))) { + _children.clear(); + _position = 0; + } + + if (DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, widget.picker.isHijri) != + DateRangePickerHelper.getNumberOfWeeksInView( + oldWidget.picker.monthViewSettings, oldWidget.picker.isHijri) || + widget.picker.monthViewSettings.firstDayOfWeek != + oldWidget.picker.monthViewSettings.firstDayOfWeek) { + _updateVisibleDates(); + _position = 0; + } + + /// Update the selection when [allowViewNavigation] property in + /// [SfDateRangePicker] changed with current picker view not as month view. + /// because year, decade and century views highlight selection when + /// [allowViewNavigation] property value as false. + if (oldWidget.picker.allowViewNavigation != + widget.picker.allowViewNavigation && + pickerView != DateRangePickerView.month) { + _position = 0; + _children.clear(); + } + + if (oldWidget.picker.controller != widget.picker.controller || + widget.picker.controller == null) { + widget.getPickerStateValues(_pickerStateDetails); + super.didUpdateWidget(oldWidget); + return; + } + + if (oldWidget.picker.controller.displayDate != + widget.picker.controller.displayDate || + !isSameDate( + _pickerStateDetails.currentDate, widget.controller.displayDate)) { + _pickerStateDetails.currentDate = widget.picker.controller.displayDate; + _updateVisibleDates(); + } + + _drawSelection(oldWidget); + widget.getPickerStateValues(_pickerStateDetails); + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + double leftPosition, rightPosition, topPosition, bottomPosition; + switch (widget.picker.navigationDirection) { + case DateRangePickerNavigationDirection.horizontal: + { + leftPosition = leftPosition ?? -widget.width; + rightPosition = rightPosition ?? -widget.width; + topPosition = 0; + bottomPosition = 0; + } + break; + case DateRangePickerNavigationDirection.vertical: + { + leftPosition = 0; + rightPosition = 0; + topPosition = topPosition ?? -widget.height; + bottomPosition = bottomPosition ?? -widget.height; + } + } + + return Stack( + children: [ + Positioned( + left: leftPosition, + right: rightPosition, + bottom: bottomPosition, + top: topPosition, + child: GestureDetector( + child: RawKeyboardListener( + focusNode: _focusNode, + onKey: _onKeyDown, + child: CustomScrollViewerLayout( + _addViews(context), + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.horizontal + ? CustomScrollDirection.horizontal + : CustomScrollDirection.vertical, + _position, + _currentChildIndex), + ), + onHorizontalDragStart: widget.picker.navigationDirection == + DateRangePickerNavigationDirection.horizontal + ? _onHorizontalStart + : null, + onHorizontalDragUpdate: widget.picker.navigationDirection == + DateRangePickerNavigationDirection.horizontal + ? _onHorizontalUpdate + : null, + onHorizontalDragEnd: widget.picker.navigationDirection == + DateRangePickerNavigationDirection.horizontal + ? _onHorizontalEnd + : null, + onVerticalDragStart: widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical + ? _onVerticalStart + : null, + onVerticalDragUpdate: widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical + ? _onVerticalUpdate + : null, + onVerticalDragEnd: widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical + ? _onVerticalEnd + : null, + ), + ) + ], + ); + } + + @override + void dispose() { + _animationController?.dispose(); + if (_animation != null) { + _animation.removeListener(_animationListener); + } + super.dispose(); + } + + void _updateVisibleDates() { + widget.getPickerStateValues(_pickerStateDetails); + final int numberOfWeeksInView = + DateRangePickerHelper.getNumberOfWeeksInView( + 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); + final dynamic nextDate = DateRangePickerHelper.getNextViewStartDate( + 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); + } + + final DateRangePickerView view = + DateRangePickerHelper.getPickerView(widget.controller.view); + + switch (view) { + case DateRangePickerView.month: + { + _visibleDates = getVisibleDates( + currentDate, + null, + widget.picker.monthViewSettings.firstDayOfWeek, + DateRangePickerHelper.getViewDatesCount( + view, numberOfWeeksInView, widget.picker.isHijri), + ); + _previousViewVisibleDates = getVisibleDates( + prevDate, + null, + widget.picker.monthViewSettings.firstDayOfWeek, + DateRangePickerHelper.getViewDatesCount( + view, numberOfWeeksInView, widget.picker.isHijri), + ); + _nextViewVisibleDates = getVisibleDates( + nextDate, + null, + widget.picker.monthViewSettings.firstDayOfWeek, + DateRangePickerHelper.getViewDatesCount( + view, numberOfWeeksInView, widget.picker.isHijri), + ); + if (widget.picker.enableMultiView) { + afterVisibleDates = getVisibleDates( + afterNextViewDate, + null, + widget.picker.monthViewSettings.firstDayOfWeek, + DateRangePickerHelper.getViewDatesCount( + view, numberOfWeeksInView, widget.picker.isHijri), + ); + } + } + break; + case DateRangePickerView.decade: + case DateRangePickerView.year: + case DateRangePickerView.century: + { + _visibleDates = DateRangePickerHelper.getVisibleYearDates( + currentDate, view, widget.picker.isHijri); + _previousViewVisibleDates = DateRangePickerHelper.getVisibleYearDates( + prevDate, view, widget.picker.isHijri); + _nextViewVisibleDates = DateRangePickerHelper.getVisibleYearDates( + nextDate, view, widget.picker.isHijri); + if (widget.picker.enableMultiView) { + afterVisibleDates = DateRangePickerHelper.getVisibleYearDates( + afterNextViewDate, view, widget.picker.isHijri); + } + } + } + + if (widget.picker.enableMultiView) { + _updateVisibleDatesForMultiView(afterVisibleDates); + } + + _currentViewVisibleDates = _visibleDates; + _pickerStateDetails.currentViewVisibleDates = _currentViewVisibleDates; + widget.updatePickerStateValues(_pickerStateDetails); + + if (_currentChildIndex == 0) { + _visibleDates = _nextViewVisibleDates; + _nextViewVisibleDates = _previousViewVisibleDates; + _previousViewVisibleDates = _currentViewVisibleDates; + } else if (_currentChildIndex == 1) { + _visibleDates = _currentViewVisibleDates; + } else if (_currentChildIndex == 2) { + _visibleDates = _previousViewVisibleDates; + _previousViewVisibleDates = _nextViewVisibleDates; + _nextViewVisibleDates = _currentViewVisibleDates; + } + } + + void _moveToNextViewWithAnimation() { + // Resets the controller to forward it again, the animation will forward + // only from the dismissed state + if (_animationController.isCompleted || _animationController.isDismissed) { + _animationController.reset(); + } else { + return; + } + + _updateSelection(); + if (widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical) { + // update the bottom to top swiping + _tween.begin = 0; + _tween.end = -widget.height; + } else { + // update the right to left swiping + _tween.begin = 0; + _tween.end = -widget.width; + } + + _animationController.duration = const Duration(milliseconds: 500); + _animationController + .forward() + .then((dynamic value) => _updateNextView()); + + /// updates the current view visible dates when the view swiped + _updateCurrentViewVisibleDates(isNextView: true); + } + + void _moveToPreviousViewWithAnimation() { + // Resets the controller to backward it again, the animation will backward + // only from the dismissed state + if (_animationController.isCompleted || _animationController.isDismissed) { + _animationController.reset(); + } else { + return; + } + + _updateSelection(); + if (widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical) { + // update the top to bottom swiping + _tween.begin = 0; + _tween.end = widget.height; + } else { + // update the left to right swiping + _tween.begin = 0; + _tween.end = widget.width; + } + + _animationController.duration = const Duration(milliseconds: 500); + _animationController + .forward() + .then((dynamic value) => _updatePreviousView()); + + /// updates the current view visible dates when the view swiped. + _updateCurrentViewVisibleDates(); + } + + void _updateVisibleDatesForMultiView(List afterVisibleDates) { + if (widget.isRtl) { + for (int i = 0; i < _visibleDates.length; i++) { + _nextViewVisibleDates.add(_visibleDates[i]); + } + for (int i = 0; i < _previousViewVisibleDates.length; i++) { + _visibleDates.add(_previousViewVisibleDates[i]); + } + for (int i = 0; i < afterVisibleDates.length; i++) { + _previousViewVisibleDates.add(afterVisibleDates[i]); + } + } else { + for (int i = 0; i < _visibleDates.length; i++) { + _previousViewVisibleDates.add(_visibleDates[i]); + } + for (int i = 0; i < _nextViewVisibleDates.length; i++) { + _visibleDates.add(_nextViewVisibleDates[i]); + } + for (int i = 0; i < afterVisibleDates.length; i++) { + _nextViewVisibleDates.add(afterVisibleDates[i]); + } + } + } + + void _updateNextViewVisibleDates() { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + final int numberOfWeeksInView = + DateRangePickerHelper.getNumberOfWeeksInView( + 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 / + (widget.picker.enableMultiView ? 4 : 2)) + .truncate()]; + } + + final DateRangePickerView view = + DateRangePickerHelper.getPickerView(widget.controller.view); + + currentViewDate = DateRangePickerHelper.getNextViewStartDate( + 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); + } + List dates; + switch (view) { + case DateRangePickerView.month: + { + dates = getVisibleDates( + currentViewDate, + null, + widget.picker.monthViewSettings.firstDayOfWeek, + DateRangePickerHelper.getViewDatesCount( + view, numberOfWeeksInView, widget.picker.isHijri), + ); + if (widget.picker.enableMultiView && !widget.isRtl) { + afterVisibleDates = getVisibleDates( + afterNextViewDate, + null, + widget.picker.monthViewSettings.firstDayOfWeek, + DateRangePickerHelper.getViewDatesCount( + view, numberOfWeeksInView, widget.picker.isHijri), + ); + } + } + break; + case DateRangePickerView.year: + case DateRangePickerView.decade: + case DateRangePickerView.century: + { + dates = DateRangePickerHelper.getVisibleYearDates( + currentViewDate, view, widget.picker.isHijri); + if (widget.picker.enableMultiView && !widget.isRtl) { + afterVisibleDates = DateRangePickerHelper.getVisibleYearDates( + afterNextViewDate, view, widget.picker.isHijri); + } + } + } + + if (widget.picker.enableMultiView) { + dates.addAll(_updateNextVisibleDateForMultiView(afterVisibleDates)); + } + + if (_currentChildIndex == 0) { + _nextViewVisibleDates = dates; + } else if (_currentChildIndex == 1) { + _previousViewVisibleDates = dates; + } else { + _visibleDates = dates; + } + } + + List _updateNextVisibleDateForMultiView( + List afterVisibleDates) { + List dates; + if (widget.picker.isHijri) { + dates = []; + } else { + dates = []; + } + if (!widget.isRtl) { + for (int i = 0; i < afterVisibleDates.length; i++) { + dates.add(afterVisibleDates[i]); + } + } else { + for (int i = 0; i < _currentViewVisibleDates.length ~/ 2; i++) { + dates.add(_currentViewVisibleDates[i]); + } + } + + return dates; + } + + void _updatePreviousViewVisibleDates() { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + final int numberOfWeeksInView = + DateRangePickerHelper.getNumberOfWeeksInView( + 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 / + (widget.picker.enableMultiView ? 4 : 2)) + .truncate()]; + } + + final DateRangePickerView view = + DateRangePickerHelper.getPickerView(widget.controller.view); + + currentViewDate = DateRangePickerHelper.getPreviousViewStartDate( + 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); + } + + switch (view) { + case DateRangePickerView.month: + { + dates = getVisibleDates( + currentViewDate, + null, + widget.picker.monthViewSettings.firstDayOfWeek, + DateRangePickerHelper.getViewDatesCount( + view, numberOfWeeksInView, widget.picker.isHijri), + ); + if (widget.picker.enableMultiView && widget.isRtl) { + afterVisibleDates = getVisibleDates( + afterNextViewDate, + null, + widget.picker.monthViewSettings.firstDayOfWeek, + DateRangePickerHelper.getViewDatesCount( + view, numberOfWeeksInView, widget.picker.isHijri), + ); + } + } + break; + case DateRangePickerView.year: + case DateRangePickerView.decade: + case DateRangePickerView.century: + { + dates = DateRangePickerHelper.getVisibleYearDates( + currentViewDate, view, widget.picker.isHijri); + if (widget.picker.enableMultiView && widget.isRtl) { + afterVisibleDates = DateRangePickerHelper.getVisibleYearDates( + afterNextViewDate, view, widget.picker.isHijri); + } + } + } + + if (widget.picker.enableMultiView) { + dates.addAll(_updatePreviousDatesForMultiView(afterVisibleDates)); + } + + if (_currentChildIndex == 0) { + _visibleDates = dates; + } else if (_currentChildIndex == 1) { + _nextViewVisibleDates = dates; + } else { + _previousViewVisibleDates = dates; + } + } + + List _updatePreviousDatesForMultiView( + List afterVisibleDates) { + List dates; + if (widget.picker.isHijri) { + dates = []; + } else { + dates = []; + } + if (widget.isRtl) { + for (int i = 0; i < (afterVisibleDates.length); i++) { + dates.add(afterVisibleDates[i]); + } + } else { + for (int i = 0; i < (_currentViewVisibleDates.length / 2); i++) { + dates.add(_currentViewVisibleDates[i]); + } + } + return dates; + } + + void _getPickerViewStateDetails(PickerStateArgs details) { + details.currentViewVisibleDates = _currentViewVisibleDates; + details.currentDate = _pickerStateDetails.currentDate; + details.selectedDate = _pickerStateDetails.selectedDate; + details.selectedDates = _pickerStateDetails.selectedDates; + details.selectedRange = _pickerStateDetails.selectedRange; + details.selectedRanges = _pickerStateDetails.selectedRanges; + details.view = _pickerStateDetails.view; + } + + void _updatePickerViewStateDetails(PickerStateArgs details) { + _pickerStateDetails.currentDate = details.currentDate; + _pickerStateDetails.selectedDate = details.selectedDate; + _pickerStateDetails.selectedDates = details.selectedDates; + _pickerStateDetails.selectedRange = details.selectedRange; + _pickerStateDetails.selectedRanges = details.selectedRanges; + _pickerStateDetails.view = details.view; + widget.updatePickerStateValues(_pickerStateDetails); + } + + _PickerView _getView(List dates, GlobalKey key) { + return _PickerView( + widget.picker, + widget.controller, + dates, + widget.width, + widget.height, + widget.datePickerTheme, + _focusNode, + widget.textScaleFactor, + key: key, + getPickerStateDetails: (PickerStateArgs details) { + _getPickerViewStateDetails(details); + }, + updatePickerStateDetails: (PickerStateArgs details) { + _updatePickerViewStateDetails(details); + }, + isRtl: widget.isRtl, + ); + } + + List _addViews(BuildContext context) { + _children = _children ?? <_PickerView>[]; + if (_children != null && _children.isEmpty) { + _previousView = _getView(_previousViewVisibleDates, _previousViewKey); + _currentView = _getView(_visibleDates, _currentViewKey); + _nextView = _getView(_nextViewVisibleDates, _nextViewKey); + + _children.add(_previousView); + _children.add(_currentView); + _children.add(_nextView); + return _children; + } + + final _PickerView previousView = _updateViews( + _previousView, _previousView.visibleDates, _previousViewVisibleDates); + final _PickerView currentView = + _updateViews(_currentView, _currentView.visibleDates, _visibleDates); + final _PickerView nextView = + _updateViews(_nextView, _nextView.visibleDates, _nextViewVisibleDates); + + /// Update views while the all day view height differ from original height, + /// else repaint the appointment painter while current child visible + /// appointment not equals picker visible appointment + if (_previousView != previousView) { + _previousView = previousView; + } + if (_currentView != currentView) { + _currentView = currentView; + } + if (_nextView != nextView) { + _nextView = nextView; + } + + return _children; + } + + // method to check and update the views and appointments on the swiping end + Widget _updateViews( + Widget view, List viewDates, List visibleDates) { + final int index = _children.indexOf(view); + // update the view with the visible dates on swiping end. + if (viewDates != visibleDates) { + view = _getView(visibleDates, view.key); + _children[index] = view; + } // check and update the visible appointments in the view + + return view; + } + + void _animationListener() { + setState(() { + _position = _animation.value; + }); + } + + void _updateSettings(_PickerScrollView oldWidget) { + //// condition to check and update the view when the settings changed, it will check each and every property of settings + //// to avoid unwanted repainting + if (oldWidget.picker.monthViewSettings != widget.picker.monthViewSettings || + oldWidget.picker.monthCellStyle != widget.picker.monthCellStyle || + oldWidget.picker.selectionRadius != widget.picker.selectionRadius || + oldWidget.picker.startRangeSelectionColor != + widget.picker.startRangeSelectionColor || + oldWidget.picker.endRangeSelectionColor != + widget.picker.endRangeSelectionColor || + oldWidget.picker.rangeSelectionColor != + widget.picker.rangeSelectionColor || + oldWidget.picker.selectionColor != widget.picker.selectionColor || + oldWidget.picker.selectionTextStyle != + widget.picker.selectionTextStyle || + oldWidget.picker.rangeTextStyle != widget.picker.rangeTextStyle || + oldWidget.picker.monthViewSettings.blackoutDates != + widget.picker.monthViewSettings.blackoutDates || + oldWidget.picker.monthViewSettings.specialDates != + widget.picker.monthViewSettings.specialDates || + oldWidget.picker.monthViewSettings.weekendDays != + widget.picker.monthViewSettings.weekendDays || + oldWidget.picker.selectionShape != widget.picker.selectionShape || + oldWidget.picker.todayHighlightColor != + widget.picker.todayHighlightColor || + oldWidget.locale != widget.locale) { + _children.clear(); + _position = 0; + } + } + + void _drawSelection(_PickerScrollView oldWidget) { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + switch (widget.picker.selectionMode) { + case DateRangePickerSelectionMode.single: + { + if ((oldWidget.picker.controller.selectedDate != + widget.picker.controller.selectedDate || + !isSameDate(_pickerStateDetails.selectedDate, + widget.controller.selectedDate))) { + _pickerStateDetails.selectedDate = + widget.picker.controller.selectedDate; + if (pickerView != DateRangePickerView.month && + !widget.picker.allowViewNavigation) { + _drawYearSelection(); + } else { + _drawMonthSelection(); + } + + _position = 0; + } + } + break; + case DateRangePickerSelectionMode.multiple: + { + if (oldWidget.picker.controller.selectedDates != + widget.picker.controller.selectedDates || + !DateRangePickerHelper.isDateCollectionEquals( + _pickerStateDetails.selectedDates, + widget.picker.controller.selectedDates)) { + _pickerStateDetails.selectedDates = + widget.picker.controller.selectedDates; + if (pickerView != DateRangePickerView.month && + !widget.picker.allowViewNavigation) { + _drawYearSelection(); + } else { + _drawMonthSelection(); + } + + _position = 0; + } + } + break; + case DateRangePickerSelectionMode.range: + { + if (oldWidget.picker.controller.selectedRange != + widget.picker.controller.selectedRange || + !DateRangePickerHelper.isRangeEquals( + _pickerStateDetails.selectedRange, + widget.picker.controller.selectedRange)) { + _pickerStateDetails.selectedRange = + widget.picker.controller.selectedRange; + if (pickerView != DateRangePickerView.month && + !widget.picker.allowViewNavigation) { + _drawYearSelection(); + } else { + _drawMonthSelection(); + } + + _position = 0; + } + } + break; + case DateRangePickerSelectionMode.multiRange: + { + if (oldWidget.picker.controller.selectedRanges != + widget.picker.controller.selectedRanges || + !DateRangePickerHelper.isDateRangesEquals( + _pickerStateDetails.selectedRanges, + widget.picker.controller.selectedRanges)) { + _pickerStateDetails.selectedRanges = + widget.picker.controller.selectedRanges; + if (pickerView != DateRangePickerView.month && + !widget.picker.allowViewNavigation) { + _drawYearSelection(); + } else { + _drawMonthSelection(); + } + + _position = 0; + } + } + } + } + + /// Update the selection details to scroll view children except current view + /// while view navigation. + void _updateSelection() { + 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 + if (pickerView != DateRangePickerView.month && + widget.picker.allowViewNavigation) { + return; + } + + widget.getPickerStateValues(_pickerStateDetails); + for (int i = 0; i < _children.length; i++) { + if (i == _currentChildIndex) { + continue; + } + + final DateRangePickerView view = + DateRangePickerHelper.getPickerView(widget.controller.view); + + final _PickerViewState viewState = _getCurrentViewState(i); + switch (view) { + case DateRangePickerView.month: + { + viewState._monthView.selectionNotifier.value = + !viewState._monthView.selectionNotifier.value; + } + break; + case DateRangePickerView.year: + case DateRangePickerView.decade: + case DateRangePickerView.century: + { + viewState._yearView.selectionNotifier.value = + !viewState._yearView.selectionNotifier.value; + } + } + } + } + + /// Draw the selection on current month view when selected date value + /// changed dynamically. + void _drawMonthSelection() { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + if (pickerView != DateRangePickerView.month || _children.isEmpty) { + return; + } + + for (int i = 0; i < _children.length; i++) { + final _PickerViewState viewState = _getCurrentViewState(i); + + /// Check the visible dates rather than current child index because + /// current child index value not updated when the selected date value + /// changed on view changed callback + if (viewState == null || + viewState._monthView.visibleDates != + _pickerStateDetails.currentViewVisibleDates) { + continue; + } + + viewState._monthView.selectionNotifier.value = + !viewState._monthView.selectionNotifier.value; + } + } + + /// 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); + if (pickerView == DateRangePickerView.month || _children.isEmpty) { + return; + } + + for (int i = 0; i < _children.length; i++) { + final _PickerViewState viewState = _getCurrentViewState(i); + + /// Check the visible dates rather than current child index because + /// current child index value not updated when the selected date value + /// changed on view changed callback + if (viewState == null || + viewState._yearView.visibleDates != + _pickerStateDetails.currentViewVisibleDates) { + continue; + } + + viewState._yearView.selectionNotifier.value = + !viewState._yearView.selectionNotifier.value; + } + } + + /// Return the picker view state details based on view index. + _PickerViewState _getCurrentViewState(int index) { + if (index == 1) { + return _currentViewKey.currentState; + } else if (index == 2) { + return _nextViewKey.currentState; + } + + return _previousViewKey.currentState; + } + + /// 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); + if (isNextView) { + if (_currentChildIndex == 0) { + _currentViewVisibleDates = _visibleDates; + } else if (_currentChildIndex == 1) { + _currentViewVisibleDates = _nextViewVisibleDates; + } else { + _currentViewVisibleDates = _previousViewVisibleDates; + } + } else { + if (_currentChildIndex == 0) { + _currentViewVisibleDates = _nextViewVisibleDates; + } else if (_currentChildIndex == 1) { + _currentViewVisibleDates = _previousViewVisibleDates; + } else { + _currentViewVisibleDates = _visibleDates; + } + } + + _pickerStateDetails.currentViewVisibleDates = _currentViewVisibleDates; + _pickerStateDetails.currentDate = _currentViewVisibleDates[0]; + final int numberOfWeeksInView = + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, widget.picker.isHijri); + if (pickerView == DateRangePickerView.month && + (numberOfWeeksInView == 6 || widget.picker.isHijri)) { + final dynamic date = _currentViewVisibleDates[ + _currentViewVisibleDates.length ~/ + (widget.picker.enableMultiView ? 4 : 2)]; + _pickerStateDetails.currentDate = DateRangePickerHelper.getDate( + date.year, date.month, 1, widget.picker.isHijri); + } + + widget.updatePickerStateValues(_pickerStateDetails); + } + + void _updateNextView() { + if (!_animationController.isCompleted) { + return; + } + + _updateNextViewVisibleDates(); + + if (_currentChildIndex == 0) { + _currentChildIndex = 1; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 0; + } + + if (kIsWeb) { + setState(() { + /// set state called to call the build method to fix the date doesn't + /// update properly issue on web, in Andriod and iOS the build method + /// called automatically when the animation ends but in web it doesn't + /// work on that way, hence we have manually called the build method by + /// adding setstate and i have logged and issue in framework once i got + /// the solution will remove this setstate + }); + } + + _resetPosition(); + } + + void _updatePreviousView() { + if (!_animationController.isCompleted) { + return; + } + + _updatePreviousViewVisibleDates(); + + if (_currentChildIndex == 0) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 0; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 1; + } + + if (kIsWeb) { + setState(() { + /// set state called to call the build method to fix the date doesn't + /// update properly issue on web, in Andriod and iOS the build method + /// called automatically when the animation ends but in web it doesn't + /// work on that way, hence we have manually called the build method by + /// adding setstate and i have logged and issue in framework once i got + /// the solution will remove this setstate + }); + } + + _resetPosition(); + } + + // 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; + } + }); + } + + /// Calculate and return the date time value based on previous selected date, + /// keyboard action and current picker view. + dynamic _getYearSelectedDate(dynamic selectedDate, PhysicalKeyboardKey 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); + if (key == PhysicalKeyboardKey.arrowRight) { + /// If index value as last cell index in current view then + /// navigate to next view. Calculate the selected index on navigated view + /// and return the selected date on navigated view on right arrow pressed + /// action. + if ((index == view.visibleDates.length - 1 || + (widget.picker.enableMultiView && + pickerView != DateRangePickerView.year && + index >= view.visibleDates.length - 3)) && + widget.picker.selectionMode == DateRangePickerSelectionMode.single) { + widget.isRtl + ? _moveToPreviousViewWithAnimation() + : _moveToNextViewWithAnimation(); + } + + if (index != -1) { + date = _updateNextYearSelectionDate(selectedDate); + } + } else if (key == PhysicalKeyboardKey.arrowLeft) { + /// If index value as first cell index in current view then + /// navigate to previous view. Calculate the selected index on navigated + /// view and return the selected date on navigated view on left arrow + /// pressed action. + if (index == 0 && + widget.picker.selectionMode == DateRangePickerSelectionMode.single) { + widget.isRtl + ? _moveToNextViewWithAnimation() + : _moveToPreviousViewWithAnimation(); + } + + if (index != -1) { + date = _updatePreviousYearSelectionDate(selectedDate); + } + } else if (key == PhysicalKeyboardKey.arrowUp) { + /// If index value not in first row then calculate the date by + /// subtracting the index value with 3 and return the date value. + if (index >= 3 && index != -1) { + index -= 3; + date = view.visibleDates[index]; + } + } else if (key == PhysicalKeyboardKey.arrowDown) { + /// If index value not in last row then calculate the date by + /// adding the index value with 3 and return the date value. + if (index <= 8 && index != -1) { + index += 3; + date = view.visibleDates[index]; + } else if (widget.picker.enableMultiView && + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical && + index <= 20 && + index != -1) { + index += 3; + date = _updateNextYearSelectionDate(selectedDate); + for (int i = 1; i < 3; i++) { + date = _updateNextYearSelectionDate(date); + } + } + } + + return date; + } + + /// Return the next date for year, decade and century view in keyboard + /// navigation + dynamic _updateNextYearSelectionDate(dynamic selectedDate) { + final int defaultRowCount = 6; + final DateRangePickerView view = + DateRangePickerHelper.getPickerView(widget.controller.view); + switch (view) { + case DateRangePickerView.month: + { + break; + } + case DateRangePickerView.year: + { + return DateRangePickerHelper.getNextViewStartDate( + DateRangePickerView.month, + defaultRowCount, + selectedDate, + widget.isRtl, + widget.picker.isHijri); + } + break; + case DateRangePickerView.decade: + { + return DateRangePickerHelper.getNextViewStartDate( + DateRangePickerView.year, + null, + selectedDate, + widget.isRtl, + widget.picker.isHijri); + } + break; + case DateRangePickerView.century: + { + return DateRangePickerHelper.getNextViewStartDate( + DateRangePickerView.decade, + null, + selectedDate, + widget.isRtl, + widget.picker.isHijri); + } + } + + return selectedDate; + } + + /// Return the previous date for year, decade and century view in keyboard + /// navigation + dynamic _updatePreviousYearSelectionDate(dynamic selectedDate) { + final int defaultRowCount = 6; + final DateRangePickerView view = + DateRangePickerHelper.getPickerView(widget.controller.view); + switch (view) { + case DateRangePickerView.month: + { + break; + } + case DateRangePickerView.year: + { + return DateRangePickerHelper.getPreviousViewStartDate( + DateRangePickerView.month, + defaultRowCount, + selectedDate, + widget.isRtl, + widget.picker.isHijri); + } + break; + case DateRangePickerView.decade: + { + return DateRangePickerHelper.getPreviousViewStartDate( + DateRangePickerView.year, + null, + selectedDate, + widget.isRtl, + widget.picker.isHijri); + } + break; + case DateRangePickerView.century: + { + return DateRangePickerHelper.getPreviousViewStartDate( + DateRangePickerView.decade, + null, + selectedDate, + widget.isRtl, + widget.picker.isHijri); + } + } + + return selectedDate; + } + + void _switchViewsByKeyBoardEvent(RawKeyEvent 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 (event.physicalKey == PhysicalKeyboardKey.digit1) { + _pickerStateDetails.view = DateRangePickerView.month; + } else if (event.physicalKey == PhysicalKeyboardKey.digit2) { + _pickerStateDetails.view = DateRangePickerView.year; + } else if (event.physicalKey == PhysicalKeyboardKey.digit3) { + _pickerStateDetails.view = DateRangePickerView.decade; + } else if (event.physicalKey == PhysicalKeyboardKey.digit4) { + _pickerStateDetails.view = DateRangePickerView.century; + } + + widget.updatePickerStateValues(_pickerStateDetails); + return; + } + } + + void _updateYearSelectionByKeyBoardNavigation( + _PickerViewState currentVisibleViewState, + _PickerView currentVisibleView, + RawKeyEvent event) { + dynamic selectedDate; + if (_pickerStateDetails.selectedDate != null && + widget.picker.selectionMode == DateRangePickerSelectionMode.single) { + selectedDate = _getYearSelectedDate(_pickerStateDetails.selectedDate, + event.physicalKey, currentVisibleView, currentVisibleViewState); + if (selectedDate != null && + DateRangePickerHelper.isBetweenMinMaxDateCell( + 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]; + selectedDate = _getYearSelectedDate( + date, event.physicalKey, 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); + } + } else if (widget.picker.selectionMode == + DateRangePickerSelectionMode.range && + _pickerStateDetails.selectedRange != null && + _pickerStateDetails.selectedRange.startDate != null && + event.isShiftPressed) { + final DateTime date = _pickerStateDetails.selectedRange.startDate; + selectedDate = _getYearSelectedDate( + date, event.physicalKey, currentVisibleView, currentVisibleViewState); + if (selectedDate != null && + DateRangePickerHelper.isBetweenMinMaxDateCell( + selectedDate, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + widget.controller.view, + widget.picker.isHijri)) { + if (_pickerStateDetails.selectedRange != null && + _pickerStateDetails.selectedRange.startDate != null && + (_pickerStateDetails.selectedRange.endDate == null || + isSameDate(_pickerStateDetails.selectedRange.startDate, + _pickerStateDetails.selectedRange.endDate))) { + _pickerStateDetails.selectedRange = PickerDateRange( + _pickerStateDetails.selectedRange.startDate, selectedDate); + } else { + _pickerStateDetails.selectedRange = + PickerDateRange(selectedDate, null); + } + } + } + + widget.updatePickerStateValues(_pickerStateDetails); + _drawYearSelection(); + } + + void _updateSelectionByKeyboardNavigation(dynamic selectedDate) { + switch (widget.picker.selectionMode) { + case DateRangePickerSelectionMode.single: + { + _pickerStateDetails.selectedDate = selectedDate; + } + break; + case DateRangePickerSelectionMode.multiple: + { + _pickerStateDetails.selectedDates.add(selectedDate); + } + break; + case DateRangePickerSelectionMode.range: + { + if (_pickerStateDetails.selectedRange != null && + _pickerStateDetails.selectedRange.startDate != null && + (_pickerStateDetails.selectedRange.endDate == null || + isSameDate(_pickerStateDetails.selectedRange.startDate, + _pickerStateDetails.selectedRange.endDate))) { + _pickerStateDetails.selectedRange = widget.picker.isHijri + ? HijriDateRange( + _pickerStateDetails.selectedRange.startDate, selectedDate) + : PickerDateRange( + _pickerStateDetails.selectedRange.startDate, selectedDate); + } else { + _pickerStateDetails.selectedRange = widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); + } + } + break; + case DateRangePickerSelectionMode.multiRange: + break; + } + } + + void _onKeyDown(RawKeyEvent event) { + if (event.runtimeType != RawKeyDownEvent) { + return; + } + + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + + _switchViewsByKeyBoardEvent(event); + + if (pickerView != DateRangePickerView.month && + widget.picker.allowViewNavigation) { + return; + } + + if (_pickerStateDetails.selectedDate == null && + (_pickerStateDetails.selectedDates == null || + _pickerStateDetails.selectedDates.isEmpty) && + _pickerStateDetails.selectedRange == null && + (_pickerStateDetails.selectedRanges == null || + _pickerStateDetails.selectedRanges.isEmpty)) { + return; + } + + _PickerViewState currentVisibleViewState; + _PickerView currentVisibleView; + if (_currentChildIndex == 0) { + currentVisibleViewState = _previousViewKey.currentState; + currentVisibleView = _previousView; + } else if (_currentChildIndex == 1) { + currentVisibleViewState = _currentViewKey.currentState; + currentVisibleView = _currentView; + } else if (_currentChildIndex == 2) { + currentVisibleViewState = _nextViewKey.currentState; + currentVisibleView = _nextView; + } + + if (pickerView != DateRangePickerView.month) { + _updateYearSelectionByKeyBoardNavigation( + currentVisibleViewState, currentVisibleView, event); + return; + } + + final dynamic selectedDate = + _updateSelectedDate(event, currentVisibleViewState, currentVisibleView); + + if (DateRangePickerHelper.isDateWithInVisibleDates( + currentVisibleView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate) || + !DateRangePickerHelper.isEnabledDate( + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + selectedDate, + widget.picker.isHijri)) { + return; + } + + final int numberOfWeeksInView = + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, widget.picker.isHijri); + if (!DateRangePickerHelper.isDateAsCurrentMonthDate( + currentVisibleView.visibleDates[ + currentVisibleView.visibleDates.length ~/ + (widget.picker.enableMultiView ? 4 : 2)], + numberOfWeeksInView, + DateRangePickerHelper.canShowLeadingAndTrailingDates( + widget.picker.monthViewSettings, widget.picker.isHijri), + selectedDate, + widget.picker.isHijri)) { + final int month = selectedDate.month; + final int nextMonth = getNextMonthDate(currentVisibleView.visibleDates[ + currentVisibleView.visibleDates.length ~/ + (widget.picker.enableMultiView ? 4 : 2)]) + .month; + if (month == nextMonth) { + widget.isRtl + ? _moveToPreviousViewWithAnimation() + : _moveToNextViewWithAnimation(); + } else { + widget.isRtl + ? _moveToNextViewWithAnimation() + : _moveToPreviousViewWithAnimation(); + } + } + + currentVisibleViewState._drawSelection(selectedDate); + + _updateSelectionByKeyboardNavigation(selectedDate); + widget.updatePickerStateValues(_pickerStateDetails); + currentVisibleViewState._monthView.selectionNotifier.value = + !currentVisibleViewState._monthView.selectionNotifier.value; + _updateSelection(); + } + + dynamic _updateSingleSelectionByKeyBoardKeys( + RawKeyEvent event, _PickerView currentView) { + if (event.physicalKey == PhysicalKeyboardKey.arrowRight) { + if (isSameDate(_pickerStateDetails.selectedDate, + currentView.visibleDates[currentView.visibleDates.length - 1])) { + _moveToNextViewWithAnimation(); + } + + return addDuration( + _pickerStateDetails.selectedDate, const Duration(days: 1)); + } else if (event.physicalKey == PhysicalKeyboardKey.arrowLeft) { + if (isSameDate( + _pickerStateDetails.selectedDate, currentView.visibleDates[0])) { + _moveToPreviousViewWithAnimation(); + } + + return subtractDuration( + _pickerStateDetails.selectedDate, const Duration(days: 1)); + } else if (event.physicalKey == PhysicalKeyboardKey.arrowUp) { + return subtractDuration(_pickerStateDetails.selectedDate, + const Duration(days: DateTime.daysPerWeek)); + } else if (event.physicalKey == PhysicalKeyboardKey.arrowDown) { + return addDuration(_pickerStateDetails.selectedDate, + const Duration(days: DateTime.daysPerWeek)); + } + return null; + } + + dynamic _updateMultiAndRangeSelectionByKeyBoard(RawKeyEvent event) { + if (event.isShiftPressed && + event.physicalKey == PhysicalKeyboardKey.arrowRight) { + if (widget.picker.selectionMode == + DateRangePickerSelectionMode.multiple) { + return addDuration( + _pickerStateDetails + .selectedDates[_pickerStateDetails.selectedDates.length - 1], + const Duration(days: 1)); + } else { + return addDuration(_pickerStateDetails.selectedRange.startDate, + const Duration(days: 1)); + } + } else if (event.isShiftPressed && + event.physicalKey == PhysicalKeyboardKey.arrowLeft) { + if (widget.picker.selectionMode == + DateRangePickerSelectionMode.multiple) { + return addDuration( + _pickerStateDetails + .selectedDates[_pickerStateDetails.selectedDates.length - 1], + const Duration(days: -1)); + } else { + return addDuration(_pickerStateDetails.selectedRange.startDate, + const Duration(days: -1)); + } + } else if (event.isShiftPressed && + event.physicalKey == PhysicalKeyboardKey.arrowUp) { + if (widget.picker.selectionMode == + DateRangePickerSelectionMode.multiple) { + return addDuration( + _pickerStateDetails + .selectedDates[_pickerStateDetails.selectedDates.length - 1], + const Duration(days: -DateTime.daysPerWeek)); + } else { + return addDuration(_pickerStateDetails.selectedRange.startDate, + const Duration(days: -DateTime.daysPerWeek)); + } + } else if (event.isShiftPressed && + event.physicalKey == PhysicalKeyboardKey.arrowDown) { + if (widget.picker.selectionMode == + DateRangePickerSelectionMode.multiple) { + return addDuration( + _pickerStateDetails + .selectedDates[_pickerStateDetails.selectedDates.length - 1], + const Duration(days: DateTime.daysPerWeek)); + } else { + return addDuration(_pickerStateDetails.selectedRange.startDate, + const Duration(days: DateTime.daysPerWeek)); + } + } + return null; + } + + dynamic _updateSelectedDate(RawKeyEvent event, _PickerViewState currentState, + _PickerView currentView) { + switch (widget.picker.selectionMode) { + case DateRangePickerSelectionMode.single: + { + return _updateSingleSelectionByKeyBoardKeys(event, currentView); + } + case DateRangePickerSelectionMode.multiple: + case DateRangePickerSelectionMode.range: + { + return _updateMultiAndRangeSelectionByKeyBoard(event); + } + case DateRangePickerSelectionMode.multiRange: + break; + } + + return null; + } + + void _onHorizontalStart(DragStartDetails dragStartDetails) { + switch (widget.picker.navigationDirection) { + case DateRangePickerNavigationDirection.horizontal: + { + _scrollStartPosition = dragStartDetails.globalPosition.dx; + _updateSelection(); + } + break; + case DateRangePickerNavigationDirection.vertical: + break; + } + } + + void _onHorizontalUpdate(DragUpdateDetails dragUpdateDetails) { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + switch (widget.picker.navigationDirection) { + case DateRangePickerNavigationDirection.horizontal: + { + final double difference = + 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)) { + 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)) { + return; + } + + _position = difference; + setState(() { + /* Updates the widget navigated distance and moves the widget + in the custom scroll view */ + }); + } + break; + case DateRangePickerNavigationDirection.vertical: + break; + } + } + + void _onHorizontalEnd(DragEndDetails dragEndDetails) { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + switch (widget.picker.navigationDirection) { + case DateRangePickerNavigationDirection.vertical: + break; + case DateRangePickerNavigationDirection.horizontal: + { + _position ??= 0; + // condition to check and update the right to left swiping + if (-_position >= widget.width / 2) { + _tween.begin = _position; + _tween.end = -widget.width; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted && _position != _tween.end) { + _animationController.reset(); + } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController + .forward() + .then((dynamic value) => _updateNextView()); + + /// updates the current view visible dates when the view swiped in + /// right to left direction + _updateCurrentViewVisibleDates(isNextView: true); + } + // fling the view from right to left + else if (-dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { + if (!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)) { + _position = 0; + setState(() { + /* Completes the swiping and rearrange the children position in + the custom scroll view */ + }); + return; + } + + _tween.begin = _position; + _tween.end = -widget.width; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted && _position != _tween.end) { + _animationController.reset(); + } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController + .fling( + velocity: 5.0, animationBehavior: AnimationBehavior.normal) + .then((dynamic value) => _updateNextView()); + + /// updates the current view visible dates when fling the view in + /// right to left direction + _updateCurrentViewVisibleDates(isNextView: true); + } + // condition to check and update the left to right swiping + else if (_position >= widget.width / 2) { + _tween.begin = _position; + _tween.end = widget.width; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController + .forward() + .then((dynamic value) => _updatePreviousView()); + + /// updates the current view visible dates when the view swiped in + /// left to right direction + _updateCurrentViewVisibleDates(); + } + // fling the view from left to right + else if (dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { + if (!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)) { + _position = 0; + setState(() { + /* Completes the swiping and rearrange the children position in + the custom scroll view */ + }); + return; + } + + _tween.begin = _position; + _tween.end = widget.width; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted && _position != _tween.end) { + _animationController.reset(); + } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController + .fling( + velocity: 5.0, animationBehavior: AnimationBehavior.normal) + .then((dynamic value) => _updatePreviousView()); + + /// updates the current view visible dates when fling the view in + /// left to right direction + _updateCurrentViewVisibleDates(); + } + // condition to check and revert the right to left swiping + else if (_position.abs() <= widget.width / 2) { + _tween.begin = _position; + _tween.end = 0.0; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted && _position != _tween.end) { + _animationController.reset(); + } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController.forward(); + } + } + } + } + + void _onVerticalStart(DragStartDetails dragStartDetails) { + switch (widget.picker.navigationDirection) { + case DateRangePickerNavigationDirection.horizontal: + break; + case DateRangePickerNavigationDirection.vertical: + { + _scrollStartPosition = dragStartDetails.globalPosition.dy; + _updateSelection(); + } + break; + } + } + + void _onVerticalUpdate(DragUpdateDetails dragUpdateDetails) { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + switch (widget.picker.navigationDirection) { + case DateRangePickerNavigationDirection.horizontal: + break; + case DateRangePickerNavigationDirection.vertical: + { + final double difference = + 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)) { + 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)) { + return; + } + + _position = difference; + setState(() { + /* Updates the widget navigated distance and moves the widget + in the custom scroll view */ + }); + } + } + } + + void _onVerticalEnd(DragEndDetails dragEndDetails) { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + switch (widget.picker.navigationDirection) { + case DateRangePickerNavigationDirection.horizontal: + break; + case DateRangePickerNavigationDirection.vertical: + { + _position ??= 0; + // condition to check and update the bottom to top swiping + if (-_position >= widget.height / 2) { + _tween.begin = _position; + _tween.end = -widget.height; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController + .forward() + .then((dynamic value) => _updateNextView()); + + /// updates the current view visible dates when the view swiped in + /// bottom to top direction + _updateCurrentViewVisibleDates(isNextView: true); + } + // fling the view to bottom to top + else if (-dragEndDetails.velocity.pixelsPerSecond.dy > + widget.height) { + if (!DateRangePickerHelper.canMoveToNextView( + 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 + the custom scroll view */ + }); + return; + } + _tween.begin = _position; + _tween.end = -widget.height; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController + .fling( + velocity: 5.0, animationBehavior: AnimationBehavior.normal) + .then((dynamic value) => _updateNextView()); + + /// updates the current view visible dates when fling the view in + /// bottom to top direction + _updateCurrentViewVisibleDates(isNextView: true); + } + // condition to check and update the top to bottom swiping + else if (_position >= widget.height / 2) { + _tween.begin = _position; + _tween.end = widget.height; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController + .forward() + .then((dynamic value) => _updatePreviousView()); + + /// updates the current view visible dates when the view swiped in + /// top to bottom direction + _updateCurrentViewVisibleDates(); + } + // fling the view to top to bottom + else if (dragEndDetails.velocity.pixelsPerSecond.dy > widget.height) { + if (!DateRangePickerHelper.canMoveToPreviousView( + 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 + the custom scroll view */ + }); + return; + } + + _tween.begin = _position; + _tween.end = widget.height; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController + .fling( + velocity: 5.0, animationBehavior: AnimationBehavior.normal) + .then((dynamic value) => _updatePreviousView()); + + /// updates the current view visible dates when fling the view in + /// top to bottom direction + _updateCurrentViewVisibleDates(); + } + // condition to check and revert the bottom to top swiping + else if (_position.abs() <= widget.height / 2) { + _tween.begin = _position; + _tween.end = 0.0; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController.forward(); + } + } + } + } +} + +/// Holds the month, year, decade and century view and handle it interactions. +@immutable +class _PickerView extends StatefulWidget { + /// Constructor to create picker view instance. + const _PickerView(this.picker, this.controller, this.visibleDates, this.width, + this.height, this.datePickerTheme, this.focusNode, this.textScaleFactor, + {Key key, + this.getPickerStateDetails, + this.updatePickerStateDetails, + this.isRtl}) + : super(key: key); + + /// Holds the visible dates for the picker view. + final List visibleDates; + + /// Holds the picker instance to access the picker details. + final _SfDateRangePicker picker; + + /// Holds the controller details used on its state + final dynamic controller; + + /// Holds the picker view width used on widget positioning. + final double width; + + /// Holds the picker view height used on widget positioning. + final double height; + + /// Used to get the picker values from date picker state. + final UpdatePickerState getPickerStateDetails; + + /// Used to update the picker values to date picker state. + final UpdatePickerState updatePickerStateDetails; + + /// Holds the theme data for date range picker. + final SfDateRangePickerThemeData datePickerTheme; + + /// Used to identify the widget direction is RTL. + final bool isRtl; + + /// Holds the node and used to request the focus. + final FocusNode focusNode; + + /// Defines the text scale factor of [SfDateRangePicker]. + final double textScaleFactor; + + @override + _PickerViewState createState() => _PickerViewState(); +} + +/// Handle the picker view children position and it interaction. +class _PickerViewState extends State<_PickerView> + with TickerProviderStateMixin { + PickerStateArgs _pickerStateDetails; + + /// Holds the month view instance used to update selection from scroll view. + MonthView _monthView; + + /// Holds the year view instance used to update selection from scroll view. + YearView _yearView; + ValueNotifier _mouseHoverPosition; + + /// The date time property used to range selection to store the + /// previous selected date value in range. + dynamic _previousSelectedDate; + + //// drag start boolean variable used to identify whether the drag started or not + //// For example., if user start drag from disabled date then the start date of the range not created + //// so in drag update method update the end date of existing selected range. + bool _isDragStart; + + /// Defines wheter the current platform is mobile or not. + bool _isMobilePlatform; + + @override + void initState() { + _pickerStateDetails = PickerStateArgs(); + _mouseHoverPosition = ValueNotifier(null); + _isDragStart = false; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final Locale locale = Localizations.localeOf(context); + final SfLocalizations localizations = SfLocalizations.of(context); + _isMobilePlatform = + DateRangePickerHelper.isMobileLayout(Theme.of(context).platform); + widget.getPickerStateDetails(_pickerStateDetails); + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + + switch (pickerView) { + case DateRangePickerView.month: + { + return GestureDetector( + child: MouseRegion( + onEnter: _pointerEnterEvent, + onHover: _pointerHoverEvent, + onExit: _pointerExitEvent, + child: + _addMonthView(locale, widget.datePickerTheme, localizations), + ), + onTapUp: _updateTapCallback, + onHorizontalDragStart: _getDragStartCallback(), + onVerticalDragStart: _getDragStartCallback(), + onHorizontalDragUpdate: _getDragUpdateCallback(), + onVerticalDragUpdate: _getDragUpdateCallback(), + ); + } + case DateRangePickerView.year: + case DateRangePickerView.decade: + case DateRangePickerView.century: + { + return GestureDetector( + child: MouseRegion( + onEnter: _pointerEnterEvent, + onHover: _pointerHoverEvent, + onExit: _pointerExitEvent, + child: _addYearView(locale, localizations), + ), + onTapUp: _updateTapCallback, + onHorizontalDragStart: _getDragStartCallback(), + onVerticalDragStart: _getDragStartCallback(), + onHorizontalDragUpdate: _getDragUpdateCallback(), + onVerticalDragUpdate: _getDragUpdateCallback(), + ); + } + } + + return null; + } + + /// Used to draw the selection on month view. + void _drawSelection(dynamic selectedDate) { + switch (widget.picker.selectionMode) { + case DateRangePickerSelectionMode.single: + _drawSingleSelectionForMonth(selectedDate); + break; + case DateRangePickerSelectionMode.multiple: + _drawMultipleSelectionForMonth(selectedDate); + break; + case DateRangePickerSelectionMode.range: + _drawRangeSelectionForMonth(selectedDate); + break; + case DateRangePickerSelectionMode.multiRange: + _drawRangesSelectionForMonth(selectedDate); + } + } + + // 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); + double viewHeaderHeight = widget.picker.monthViewSettings.viewHeaderHeight; + if (pickerView == DateRangePickerView.month && + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical) { + viewHeaderHeight = 0; + } + + final double height = widget.height - viewHeaderHeight; + _monthView = _getMonthView( + locale, widget.datePickerTheme, localizations, widget.width, height); + return Stack( + children: [ + _getViewHeader(viewHeaderHeight, locale, datePickerTheme), + Positioned( + left: 0, + top: viewHeaderHeight, + right: 0, + height: height, + child: RepaintBoundary( + child: _monthView, + ), + ), + ], + ); + } + + MonthView _getMonthView( + Locale locale, + SfDateRangePickerThemeData datePickerTheme, + SfLocalizations localizations, + double width, + double height) { + final int rowCount = DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, widget.picker.isHijri); + return MonthView( + widget.visibleDates, + rowCount, + widget.picker.monthCellStyle, + widget.picker.selectionTextStyle, + widget.picker.rangeTextStyle, + widget.picker.selectionColor, + widget.picker.startRangeSelectionColor, + widget.picker.endRangeSelectionColor, + widget.picker.rangeSelectionColor, + widget.datePickerTheme, + widget.isRtl, + widget.picker.todayHighlightColor, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + DateRangePickerHelper.canShowLeadingAndTrailingDates( + widget.picker.monthViewSettings, widget.picker.isHijri), + widget.picker.monthViewSettings.blackoutDates, + widget.picker.monthViewSettings.specialDates, + widget.picker.monthViewSettings.weekendDays, + widget.picker.selectionShape, + widget.picker.selectionRadius, + _mouseHoverPosition, + widget.picker.enableMultiView, + widget.picker.viewSpacing, + ValueNotifier(false), + widget.textScaleFactor, + widget.picker.selectionMode, + widget.picker.isHijri, + localizations, + widget.picker.navigationDirection, + width, + height, + widget.getPickerStateDetails, + widget.picker.cellBuilder); + } + + Widget _getViewHeader(double viewHeaderHeight, Locale locale, + SfDateRangePickerThemeData datePickerTheme) { + if (viewHeaderHeight == 0) { + return Positioned( + left: 0, + top: 0, + right: 0, + height: viewHeaderHeight, + child: Container()); + } + + final Color todayTextColor = + widget.picker.monthCellStyle.todayTextStyle != null && + widget.picker.monthCellStyle.todayTextStyle.color != null + ? widget.picker.monthCellStyle.todayTextStyle.color + : (widget.picker.todayHighlightColor != null && + widget.picker.todayHighlightColor != Colors.transparent + ? widget.picker.todayHighlightColor + : widget.datePickerTheme.todayHighlightColor); + + return Positioned( + left: 0, + top: 0, + right: 0, + height: viewHeaderHeight, + child: Container( + color: + widget.picker.monthViewSettings.viewHeaderStyle.backgroundColor ?? + widget.datePickerTheme.viewHeaderBackgroundColor, + child: RepaintBoundary( + child: CustomPaint( + painter: _PickerViewHeaderPainter( + widget.visibleDates, + widget.picker.monthViewSettings.viewHeaderStyle, + viewHeaderHeight, + widget.picker.monthViewSettings, + widget.datePickerTheme, + locale, + widget.isRtl, + widget.picker.monthCellStyle, + widget.picker.enableMultiView, + widget.picker.viewSpacing, + todayTextColor, + widget.textScaleFactor, + widget.picker.isHijri, + widget.picker.navigationDirection), + ), + ), + ), + ); + } + + void _updateTapCallback(TapUpDetails details) { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + switch (pickerView) { + case DateRangePickerView.month: + { + double viewHeaderHeight = + widget.picker.monthViewSettings.viewHeaderHeight; + if (widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical) { + viewHeaderHeight = 0; + } + + if (details.localPosition.dy < viewHeaderHeight) { + return; + } + + if (details.localPosition.dy > viewHeaderHeight) { + _handleTouch( + Offset(details.localPosition.dx, + details.localPosition.dy - viewHeaderHeight), + details); + } + } + break; + case DateRangePickerView.year: + case DateRangePickerView.decade: + case DateRangePickerView.century: + { + _handleYearPanelSelection( + Offset(details.localPosition.dx, details.localPosition.dy)); + } + } + + if (!widget.focusNode.hasFocus) { + widget.focusNode.requestFocus(); + } + } + + void _updateMouseHover(Offset globalPosition) { + if (_isMobilePlatform) { + return; + } + + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + final RenderBox box = context.findRenderObject(); + final Offset localPosition = box.globalToLocal(globalPosition); + final double viewHeaderHeight = pickerView == DateRangePickerView.month && + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.horizontal + ? widget.picker.monthViewSettings.viewHeaderHeight + : 0; + final double xPosition = localPosition.dx; + final double yPosition = localPosition.dy - viewHeaderHeight; + + if (localPosition.dy < viewHeaderHeight) { + return; + } + + _mouseHoverPosition.value = Offset(xPosition, yPosition); + } + + void _pointerEnterEvent(PointerEnterEvent event) { + _updateMouseHover(event.position); + } + + void _pointerHoverEvent(PointerHoverEvent event) { + _updateMouseHover(event.position); + } + + void _pointerExitEvent(PointerExitEvent event) { + _mouseHoverPosition.value = null; + } + + Widget _addYearView(Locale locale, SfLocalizations localizations) { + _yearView = _getYearView(locale, localizations); + 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.picker.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); + } + + GestureDragStartCallback _getDragStartCallback() { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + //// return drag start start event when selection mode as range or multi range. + if ((pickerView != DateRangePickerView.month && + widget.picker.allowViewNavigation) || + !widget.picker.monthViewSettings.enableSwipeSelection) { + return null; + } + + if (widget.picker.selectionMode != DateRangePickerSelectionMode.range && + widget.picker.selectionMode != + DateRangePickerSelectionMode.multiRange) { + return null; + } + + switch (pickerView) { + case DateRangePickerView.month: + { + return _dragStart; + } + case DateRangePickerView.year: + case DateRangePickerView.decade: + case DateRangePickerView.century: + return _dragStartOnYear; + } + + return null; + } + + GestureDragUpdateCallback _getDragUpdateCallback() { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + //// return drag update start event when selection mode as range or multi range. + if ((pickerView != DateRangePickerView.month && + widget.picker.allowViewNavigation) || + !widget.picker.monthViewSettings.enableSwipeSelection) { + return null; + } + + if (widget.picker.selectionMode != DateRangePickerSelectionMode.range && + widget.picker.selectionMode != + DateRangePickerSelectionMode.multiRange) { + return null; + } + + switch (pickerView) { + case DateRangePickerView.month: + { + return _dragUpdate; + } + case DateRangePickerView.year: + case DateRangePickerView.decade: + case DateRangePickerView.century: + { + return _dragUpdateOnYear; + } + } + + return null; + } + + int _getYearViewIndex(double xPosition, double yPosition) { + int rowIndex, columnIndex; + int columnCount = YearView.maxColumnCount; + double width = widget.width; + double height = widget.height; + int rowCount = YearView.maxRowCount; + int index = -1; + if (widget.picker.enableMultiView) { + switch (widget.picker.navigationDirection) { + case DateRangePickerNavigationDirection.horizontal: + { + columnCount *= 2; + width -= widget.picker.viewSpacing; + if (xPosition > width / 2 && + xPosition < (width / 2) + widget.picker.viewSpacing) { + return index; + } else if (xPosition > width / 2) { + xPosition -= widget.picker.viewSpacing; + } + } + break; + case DateRangePickerNavigationDirection.vertical: + { + rowCount *= 2; + height = (height - widget.picker.viewSpacing) / 2; + if (yPosition > height && + yPosition < height + widget.picker.viewSpacing) { + return index; + } else if (yPosition > height) { + yPosition -= widget.picker.viewSpacing; + } + } + } + } + + final double cellWidth = width / columnCount; + final double cellHeight = height / YearView.maxRowCount; + if (yPosition < 0 || xPosition < 0) { + return index; + } + + rowIndex = xPosition ~/ cellWidth; + if (rowIndex >= columnCount) { + rowIndex = columnCount - 1; + } else if (rowIndex < 0) { + return index; + } + + columnIndex = yPosition ~/ cellHeight; + if (columnIndex >= rowCount) { + columnIndex = rowCount - 1; + } else if (columnIndex < 0) { + return index; + } + + if (widget.isRtl) { + rowIndex = DateRangePickerHelper.getRtlIndex(columnCount, rowIndex); + if (widget.picker.enableMultiView && + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical) { + if (columnIndex > YearView.maxColumnCount) { + columnIndex -= (YearView.maxColumnCount + 1); + } else { + columnIndex += (YearView.maxColumnCount + 1); + } + } + } + + const int totalDatesCount = YearView.maxRowCount * YearView.maxColumnCount; + index = (columnIndex * YearView.maxColumnCount) + + ((rowIndex ~/ YearView.maxColumnCount) * totalDatesCount) + + (rowIndex % YearView.maxColumnCount); + return widget.picker.enableMultiView && + DateRangePickerHelper.isLeadingCellDate( + index, + (index ~/ totalDatesCount) * totalDatesCount, + widget.visibleDates, + widget.controller.view) + ? -1 + : index; + } + + int _getSelectedIndex(double xPosition, double yPosition) { + int rowIndex, columnIndex; + double width = widget.width; + double height = widget.height; + int index = -1; + int totalColumnCount = DateTime.daysPerWeek; + final int rowCount = DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, widget.picker.isHijri); + int totalRowCount = rowCount; + if (widget.picker.enableMultiView) { + switch (widget.picker.navigationDirection) { + case DateRangePickerNavigationDirection.horizontal: + { + width -= widget.picker.viewSpacing; + totalColumnCount *= 2; + if (xPosition > width / 2 && + xPosition < (width / 2) + widget.picker.viewSpacing) { + return index; + } else if (xPosition > width / 2) { + xPosition -= widget.picker.viewSpacing; + } + } + break; + case DateRangePickerNavigationDirection.vertical: + { + height = (height - widget.picker.viewSpacing) / 2; + totalRowCount *= 2; + if (yPosition > height && + yPosition < height + widget.picker.viewSpacing) { + return index; + } else if (yPosition > height) { + yPosition -= widget.picker.viewSpacing; + } + } + } + } + + if (yPosition < 0 || xPosition < 0) { + return index; + } + + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + + double viewHeaderHeight = widget.picker.monthViewSettings.viewHeaderHeight; + if (pickerView == DateRangePickerView.month && + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical) { + viewHeaderHeight = 0; + } + + final double cellWidth = width / totalColumnCount; + final double cellHeight = (height - viewHeaderHeight) / rowCount; + rowIndex = (xPosition / cellWidth).truncate(); + if (rowIndex >= totalColumnCount) { + rowIndex = totalColumnCount - 1; + } else if (rowIndex < 0) { + return index; + } + + columnIndex = (yPosition / cellHeight).truncate(); + if (columnIndex >= totalRowCount) { + columnIndex = totalRowCount - 1; + } else if (columnIndex < 0) { + return index; + } + + if (widget.isRtl) { + rowIndex = DateRangePickerHelper.getRtlIndex(totalColumnCount, rowIndex); + if (widget.picker.enableMultiView && + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical) { + if (columnIndex >= rowCount) { + columnIndex -= rowCount; + } else { + columnIndex += rowCount; + } } + } - _controller.view ??= _view; - _controller.addPropertyChangedListener(_pickerValueChangedListener); - _initNavigation(); - _updateSelectionValues(); - _view = _controller.view; + index = (columnIndex * DateTime.daysPerWeek) + + ((rowIndex ~/ DateTime.daysPerWeek) * + (totalRowCount * DateTime.daysPerWeek)) + + (rowIndex % DateTime.daysPerWeek); + return index; + } + + void _dragStart(DragStartDetails details) { + //// Set drag start value as false, identifies the start date of the range not updated. + _isDragStart = false; + widget.getPickerStateDetails(_pickerStateDetails); + final double xPosition = details.localPosition.dx; + 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 - + widget.picker.monthViewSettings.viewHeaderHeight; } - if (oldWidget.selectionMode != widget.selectionMode) { - _updateSelectionValues(); + final int index = _getSelectedIndex(xPosition, yPosition); + if (index == -1) { + return; } - if (oldWidget.minDate != widget.minDate || - oldWidget.maxDate != widget.maxDate) { - _currentDate = getValidDate(widget.minDate, widget.maxDate, _currentDate); + final dynamic selectedDate = widget.visibleDates[index]; + if (!DateRangePickerHelper.isEnabledDate( + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + selectedDate, + widget.picker.isHijri)) { + return; } - if (widget.monthViewSettings.numberOfWeeksInView != - oldWidget.monthViewSettings.numberOfWeeksInView) { - _currentDate = _updateCurrentDate(oldWidget); - _controller.displayDate = _currentDate; + 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)) { + return; } - if (oldWidget.controller != widget.controller || - widget.controller == null) { - super.didUpdateWidget(oldWidget); + if (DateRangePickerHelper.isDateWithInVisibleDates(widget.visibleDates, + widget.picker.monthViewSettings.blackoutDates, selectedDate)) { return; } - if (oldWidget.controller.selectedDate != widget.controller.selectedDate) { - _selectedDate = _controller.selectedDate; + //// Set drag start value as false, identifies the start date of the range updated. + _isDragStart = true; + _updateSelectedRangesOnDragStart(_monthView, selectedDate); + + /// Assign start date of the range as previous selected date. + _previousSelectedDate = selectedDate; + + widget.updatePickerStateDetails(_pickerStateDetails); + _monthView.selectionNotifier.value = !_monthView.selectionNotifier.value; + } + + void _dragUpdate(DragUpdateDetails details) { + widget.getPickerStateDetails(_pickerStateDetails); + final double xPosition = details.localPosition.dx; + double yPosition = details.localPosition.dy; + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + if (pickerView == DateRangePickerView.month && + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.horizontal) { + yPosition = details.localPosition.dy - + widget.picker.monthViewSettings.viewHeaderHeight; } - if (oldWidget.controller.selectedDates != widget.controller.selectedDates) { - _selectedDates = _cloneList(_controller.selectedDates); + final int index = _getSelectedIndex(xPosition, yPosition); + if (index == -1) { + return; } - if (oldWidget.controller.selectedRange != widget.controller.selectedRange) { - _selectedRange = _controller.selectedRange; + final dynamic selectedDate = widget.visibleDates[index]; + if (!DateRangePickerHelper.isEnabledDate( + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + selectedDate, + widget.picker.isHijri)) { + return; } - if (oldWidget.controller.selectedRanges != - widget.controller.selectedRanges) { - _selectedRanges = _cloneList(_controller.selectedRanges); + 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)) { + return; } - if (oldWidget.controller.view != widget.controller.view) { - _view = _controller.view; - _currentDate = _updateCurrentDate(oldWidget); - _controller.displayDate = _currentDate; + if (DateRangePickerHelper.isDateWithInVisibleDates(widget.visibleDates, + widget.picker.monthViewSettings.blackoutDates, selectedDate)) { + return; } - if (oldWidget.controller.displayDate != widget.controller.displayDate && - widget.controller.displayDate != null) { - _currentDate = - getValidDate(widget.minDate, widget.maxDate, _controller.displayDate); - _controller.displayDate = _currentDate; + _updateSelectedRangesOnDragUpdateMonth(selectedDate); + + /// Assign start date of the range as previous selected date. + _previousSelectedDate = selectedDate; + + //// Set drag start value as false, identifies the start date of the range updated. + _isDragStart = true; + widget.updatePickerStateDetails(_pickerStateDetails); + _monthView.selectionNotifier.value = !_monthView.selectionNotifier.value; + } + + void _updateSelectedRangesOnDragStart(dynamic view, dynamic selectedDate) { + switch (widget.picker.selectionMode) { + case DateRangePickerSelectionMode.single: + case DateRangePickerSelectionMode.multiple: + break; + case DateRangePickerSelectionMode.range: + { + _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)); + _removeInterceptRanges( + _pickerStateDetails.selectedRanges, + _pickerStateDetails.selectedRanges[ + _pickerStateDetails.selectedRanges.length - 1]); + } } + } - super.didUpdateWidget(oldWidget); + void _updateSelectedRangesOnDragUpdateMonth(dynamic selectedDate) { + switch (widget.picker.selectionMode) { + case DateRangePickerSelectionMode.single: + case DateRangePickerSelectionMode.multiple: + break; + case DateRangePickerSelectionMode.range: + { + //// 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); + } else { + if (_pickerStateDetails.selectedRange != null && + _pickerStateDetails.selectedRange.startDate != null) { + final dynamic updatedRange = _getSelectedRangeOnDragUpdate( + _pickerStateDetails.selectedRange, selectedDate); + if (DateRangePickerHelper.isRangeEquals( + _pickerStateDetails.selectedRange, updatedRange)) { + return; + } + + _pickerStateDetails.selectedRange = updatedRange; + } else { + _pickerStateDetails.selectedRange = widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); + } + } + } + break; + case DateRangePickerSelectionMode.multiRange: + { + _pickerStateDetails.selectedRanges ??= []; + final int count = _pickerStateDetails.selectedRanges.length; + dynamic _lastRange; + if (count > 0) { + _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)); + } else { + if (_lastRange != null && _lastRange.startDate != null) { + final dynamic updatedRange = + _getSelectedRangeOnDragUpdate(_lastRange, selectedDate); + if (DateRangePickerHelper.isRangeEquals( + _lastRange, updatedRange)) { + return; + } + + _pickerStateDetails.selectedRanges[count - 1] = updatedRange; + } else { + _pickerStateDetails.selectedRanges.add(widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null)); + } + } + + _removeInterceptRanges( + _pickerStateDetails.selectedRanges, + _pickerStateDetails.selectedRanges[ + _pickerStateDetails.selectedRanges.length - 1]); + } + } } - @override - Widget build(BuildContext context) { - double top = 0, height; - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - _minWidth = constraints.maxWidth == double.infinity - ? _minWidth - : constraints.maxWidth; - _minHeight = constraints.maxHeight == double.infinity - ? _minHeight - : constraints.maxHeight; + /// Return the range that start date is before of end date in month view. + dynamic _getSelectedRangeOnDragUpdate( + dynamic previousRange, dynamic selectedDate) { + final dynamic previousRangeStartDate = previousRange.startDate; + final dynamic previousRangeEndDate = + previousRange.endDate ?? previousRange.startDate; + dynamic rangeStartDate = previousRangeStartDate; + dynamic rangeEndDate = selectedDate; + if (isSameDate(previousRangeStartDate, _previousSelectedDate)) { + if (isSameOrBeforeDate(previousRangeEndDate, rangeEndDate)) { + rangeStartDate = selectedDate; + rangeEndDate = previousRangeEndDate; + } else { + rangeStartDate = previousRangeEndDate; + rangeEndDate = selectedDate; + } + } else if (isSameDate(previousRangeEndDate, _previousSelectedDate)) { + if (isSameOrAfterDate(previousRangeStartDate, rangeEndDate)) { + rangeStartDate = previousRangeStartDate; + rangeEndDate = selectedDate; + } else { + rangeStartDate = selectedDate; + rangeEndDate = previousRangeStartDate; + } + } - height = _minHeight - widget.headerHeight; - top = widget.headerHeight; - if (_view == DateRangePickerView.month && - widget.navigationDirection == - DateRangePickerNavigationDirection.vertical) { - height -= widget.monthViewSettings.viewHeaderHeight; - top += widget.monthViewSettings.viewHeaderHeight; + if (widget.picker.isHijri) { + return HijriDateRange(rangeStartDate, rangeEndDate); + } + + return PickerDateRange(rangeStartDate, rangeEndDate); + } + + /// Return the range that start date is before of end date in year view. + dynamic _getSelectedRangeOnDragUpdateYear( + 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 (_isSameOrBeforeDateCell(previousRangeEndDate, rangeEndDate)) { + rangeStartDate = selectedDate; + rangeEndDate = previousRangeEndDate; + } else { + rangeStartDate = previousRangeEndDate; + rangeEndDate = selectedDate; + } + } else if (DateRangePickerHelper.isSameCellDates( + previousRangeEndDate, _previousSelectedDate, widget.controller.view)) { + if (_isSameOrAfterDateCell(previousRangeStartDate, rangeEndDate)) { + rangeStartDate = previousRangeStartDate; + rangeEndDate = selectedDate; + } else { + rangeStartDate = selectedDate; + rangeEndDate = previousRangeStartDate; } + } - return Container( - width: _minWidth, - height: _minHeight, - color: widget.backgroundColor ?? _datePickerTheme.backgroundColor, - child: _addChildren(top, height, _minWidth), - ); - }); + rangeEndDate = DateRangePickerHelper.getLastDate( + rangeEndDate, widget.controller.view, widget.picker.isHijri); + if (widget.picker.maxDate != null) { + rangeEndDate = rangeEndDate.isAfter(widget.picker.maxDate) + ? widget.picker.maxDate + : rangeEndDate; + } + rangeStartDate = _getFirstDate(rangeStartDate, widget.picker.isHijri); + if (widget.picker.minDate != null) { + rangeStartDate = rangeStartDate.isBefore(widget.picker.minDate) + ? widget.picker.minDate + : rangeStartDate; + } + + if (widget.picker.isHijri) { + return HijriDateRange(rangeStartDate, rangeEndDate); + } + + return PickerDateRange(rangeStartDate, rangeEndDate); } - @override - void dispose() { - _controller.removePropertyChangedListener(_pickerValueChangedListener); - _controller._backward = null; - _controller._forward = null; - super.dispose(); + /// Return the first date of the month, year and decade based on view. + /// Note: This method not applicable for month view. + dynamic _getFirstDate(dynamic date, bool isHijri) { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + if (pickerView == DateRangePickerView.month) { + return date; + } + + if (pickerView == DateRangePickerView.year) { + return DateRangePickerHelper.getDate(date.year, date.month, 1, isHijri); + } else if (pickerView == DateRangePickerView.decade) { + return DateRangePickerHelper.getDate(date.year, 1, 1, isHijri); + } else if (pickerView == DateRangePickerView.century) { + return DateRangePickerHelper.getDate( + (date.year ~/ 10) * 10, 1, 1, isHijri); + } + + return date; } - void _initNavigation() { - _controller._forward = _moveToNextView; - _controller._backward = _moveToPreviousView; + /// Check the date cell is same or before of the max date cell. + /// If picker max date as 20-12-2020 and selected date value as 21-12-2020 + /// 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. + bool _isSameOrBeforeDateCell(dynamic currentMaxDate, dynamic currentDate) { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + if (pickerView == DateRangePickerView.year) { + return (currentDate.month <= currentMaxDate.month && + currentDate.year == currentMaxDate.year) || + currentDate.year < currentMaxDate.year; + } else if (pickerView == DateRangePickerView.decade) { + return currentDate.year <= currentMaxDate.year; + } else if (pickerView == DateRangePickerView.century) { + return (currentDate.year ~/ 10) <= (currentMaxDate.year ~/ 10); + } + + return false; } - void _initPickerController() { - _controller = widget.controller ?? DateRangePickerController(); - _controller.selectedDate = widget.initialSelectedDate; - _controller.selectedDates = _cloneList(widget.initialSelectedDates); - _controller.selectedRange = widget.initialSelectedRange; - _controller.selectedRanges = _cloneList(widget.initialSelectedRanges); - _controller.view = widget.view; - _currentDate = - getValidDate(widget.minDate, widget.maxDate, widget.initialDisplayDate); - _controller.displayDate = _currentDate; + /// Check the date cell is same or after of the min date cell. + /// If picker min date as 20-12-2020 and selected date value as 10-12-2020 + /// then the year view need to highlight selection because year view only + /// 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); + if (pickerView == DateRangePickerView.year) { + return (currentDate.month >= currentMinDate.month && + currentDate.year == currentMinDate.year) || + currentDate.year > currentMinDate.year; + } else if (pickerView == DateRangePickerView.decade) { + return currentDate.year >= currentMinDate.year; + } else if (pickerView == DateRangePickerView.century) { + return (currentDate.year ~/ 10) >= (currentMinDate.year ~/ 10); + } + + return false; } - void _updateSelectionValues() { - _selectedDate = _controller.selectedDate; - _selectedDates = _cloneList(_controller.selectedDates); - _selectedRange = _controller.selectedRange; - _selectedRanges = _cloneList(_controller.selectedRanges); + void _updateSelectedRangesOnDragUpdateYear(dynamic selectedDate) { + switch (widget.picker.selectionMode) { + case DateRangePickerSelectionMode.single: + case DateRangePickerSelectionMode.multiple: + break; + case DateRangePickerSelectionMode.range: + { + //// 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); + } else { + if (_pickerStateDetails.selectedRange != null && + _pickerStateDetails.selectedRange.startDate != null) { + final dynamic updatedRange = _getSelectedRangeOnDragUpdateYear( + _pickerStateDetails.selectedRange, selectedDate); + if (DateRangePickerHelper.isRangeEquals( + _pickerStateDetails.selectedRange, updatedRange)) { + return; + } + + _pickerStateDetails.selectedRange = updatedRange; + } else { + _pickerStateDetails.selectedRange = widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); + } + } + } + break; + case DateRangePickerSelectionMode.multiRange: + { + _pickerStateDetails.selectedRanges ??= []; + final int count = _pickerStateDetails.selectedRanges.length; + dynamic _lastRange; + if (count > 0) { + _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)); + } else { + if (_lastRange != null && _lastRange.startDate != null) { + final dynamic updatedRange = + _getSelectedRangeOnDragUpdateYear(_lastRange, selectedDate); + if (DateRangePickerHelper.isRangeEquals( + _lastRange, updatedRange)) { + return; + } + + _pickerStateDetails.selectedRanges[count - 1] = updatedRange; + } else { + _pickerStateDetails.selectedRanges.add(widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null)); + } + } + + _removeInterceptRanges( + _pickerStateDetails.selectedRanges, + _pickerStateDetails.selectedRanges[ + _pickerStateDetails.selectedRanges.length - 1]); + } + } } - void _pickerValueChangedListener(String value) { - if (value == _kSelectedDateString) { - _raiseSelectionChangedCallback(widget, value: _controller.selectedDate); - if (!mounted || isSameDate(_selectedDate, _controller.selectedDate)) { + void _dragStartOnYear(DragStartDetails details) { + //// 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); + 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)) { + return; + } + + //// Set drag start value as false, identifies the start date of the range updated. + _isDragStart = true; + _updateSelectedRangesOnDragStart(_yearView, selectedDate); + _previousSelectedDate = selectedDate; + + widget.updatePickerStateDetails(_pickerStateDetails); + _yearView.selectionNotifier.value = !_yearView.selectionNotifier.value; + } + + void _dragUpdateOnYear(DragUpdateDetails details) { + widget.getPickerStateDetails(_pickerStateDetails); + 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)) { + return; + } + + _updateSelectedRangesOnDragUpdateYear(selectedDate); + _previousSelectedDate = selectedDate; + + //// Set drag start value as false, identifies the start date of the range updated. + _isDragStart = true; + widget.updatePickerStateDetails(_pickerStateDetails); + _yearView.selectionNotifier.value = !_yearView.selectionNotifier.value; + } + + void _handleTouch(Offset details, TapUpDetails tapUpDetails) { + widget.getPickerStateDetails(_pickerStateDetails); + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + if (pickerView == DateRangePickerView.month) { + final int index = _getSelectedIndex(details.dx, details.dy); + if (index == -1) { return; } - setState(() { - _selectedDate = _controller.selectedDate; - }); - } else if (value == _kSelectedDatesString) { - _raiseSelectionChangedCallback(widget, value: _controller.selectedDates); - if (!mounted || - _isDateCollectionEquals(_selectedDates, _controller.selectedDates)) { + final dynamic selectedDate = widget.visibleDates[index]; + if (!DateRangePickerHelper.isEnabledDate( + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + selectedDate, + widget.picker.isHijri)) { return; } - setState(() { - _selectedDates = _cloneList(_controller.selectedDates); - }); - } else if (value == _kSelectedRangeString) { - _raiseSelectionChangedCallback(widget, value: _controller.selectedRange); - if (!mounted || - _isRangeEquals(_selectedRange, _controller.selectedRange)) { + 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)) { return; } - setState(() { - _selectedRange = _controller.selectedRange; - }); - } else if (value == _kSelectedRangesString) { - _raiseSelectionChangedCallback(widget, value: _controller.selectedRanges); - if (!mounted || - _isDateRangesEquals(_selectedRanges, _controller.selectedRanges)) { + if (DateRangePickerHelper.isDateWithInVisibleDates(widget.visibleDates, + widget.picker.monthViewSettings.blackoutDates, selectedDate)) { return; } - setState(() { - _selectedRanges = _cloneList(_controller.selectedRanges); - }); - } else if (value == _kViewString) { - if (!mounted || _view == _controller.view) { - return; + _drawSelection(selectedDate); + widget.updatePickerStateDetails(_pickerStateDetails); + _monthView.selectionNotifier.value = !_monthView.selectionNotifier.value; + } + } + + int _getCurrentDateIndex(int index) { + final int datesCount = DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, widget.picker.isHijri) * + DateTime.daysPerWeek; + int currentMonthIndex = datesCount ~/ 2; + if (widget.picker.enableMultiView && index >= datesCount) { + currentMonthIndex += datesCount; + } + + return currentMonthIndex; + } + + void _drawSingleSelectionForYear(dynamic selectedDate) { + if (widget.picker.toggleDaySelection && + DateRangePickerHelper.isSameCellDates(selectedDate, + _pickerStateDetails.selectedDate, widget.controller.view)) { + selectedDate = null; + } + + _pickerStateDetails.selectedDate = selectedDate; + } + + void _drawMultipleSelectionForYear(dynamic selectedDate) { + int selectedIndex = -1; + if (_pickerStateDetails.selectedDates != null && + _pickerStateDetails.selectedDates.isNotEmpty) { + selectedIndex = DateRangePickerHelper.getDateCellIndex( + _pickerStateDetails.selectedDates, + selectedDate, + widget.controller.view); + } + + if (selectedIndex == -1) { + _pickerStateDetails.selectedDates ??= []; + _pickerStateDetails.selectedDates.add(selectedDate); + } else { + _pickerStateDetails.selectedDates.removeAt(selectedIndex); + } + } + + void _drawRangeSelectionForYear(dynamic selectedDate) { + if (_pickerStateDetails.selectedRange != null && + _pickerStateDetails.selectedRange.startDate != null && + (_pickerStateDetails.selectedRange.endDate == null || + DateRangePickerHelper.isSameCellDates( + _pickerStateDetails.selectedRange.startDate, + _pickerStateDetails.selectedRange.endDate, + widget.controller.view))) { + dynamic startDate = _pickerStateDetails.selectedRange.startDate; + dynamic endDate = selectedDate; + if (startDate.isAfter(endDate)) { + final dynamic temp = startDate; + startDate = endDate; + endDate = temp; } - setState(() { - _view = _controller.view; - _scrollViewKey.currentState._position = 0.0; - _scrollViewKey.currentState._children.clear(); - _scrollViewKey.currentState._updateVisibleDates(); - }); - } else if (value == _kDisplayDateString) { - if (!isSameOrAfterDate(widget.minDate, _controller.displayDate)) { - _controller.displayDate = widget.minDate; - return; + endDate = DateRangePickerHelper.getLastDate( + endDate, widget.controller.view, widget.picker.isHijri); + if (widget.picker.maxDate != null) { + endDate = endDate.isAfter(widget.picker.maxDate) + ? widget.picker.maxDate + : endDate; } - if (!isSameOrBeforeDate(widget.maxDate, _controller.displayDate)) { - _controller.displayDate = widget.maxDate; - return; + if (widget.picker.minDate != null) { + startDate = startDate.isBefore(widget.picker.minDate) + ? widget.picker.minDate + : startDate; } - //// Restrict the update when current visible dates holds updated display date. - if (isSameDate(_currentDate, _controller.displayDate) || - _checkDateWithInVisibleDates(_controller.displayDate)) { - _currentDate = _controller.displayDate; - return; + _pickerStateDetails.selectedRange = widget.picker.isHijri + ? HijriDateRange(startDate, endDate) + : PickerDateRange(startDate, endDate); + } else { + _pickerStateDetails.selectedRange = widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); + } + } + + void _drawRangesSelectionForYear(dynamic selectedDate) { + _pickerStateDetails.selectedRanges ??= []; + int count = _pickerStateDetails.selectedRanges.length; + dynamic _lastRange; + if (count > 0) { + _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; + dynamic endDate = selectedDate; + if (startDate.isAfter(endDate)) { + final dynamic temp = startDate; + startDate = endDate; + endDate = temp; + } + + endDate = DateRangePickerHelper.getLastDate( + endDate, widget.controller.view, widget.picker.isHijri); + if (widget.picker.maxDate != null) { + endDate = endDate.isAfter(widget.picker.maxDate) + ? widget.picker.maxDate + : endDate; + } + + if (widget.picker.minDate != null) { + startDate = startDate.isBefore(widget.picker.minDate) + ? widget.picker.minDate + : startDate; } - if (!mounted) { + 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)); + } + + count = _pickerStateDetails.selectedRanges.length; + _removeInterceptRanges( + _pickerStateDetails.selectedRanges, + _pickerStateDetails + .selectedRanges[_pickerStateDetails.selectedRanges.length - 1]); + _lastRange = _pickerStateDetails + .selectedRanges[_pickerStateDetails.selectedRanges.length - 1]; + if (count != _pickerStateDetails.selectedRanges.length && + (_lastRange.endDate == null || + DateRangePickerHelper.isSameCellDates(_lastRange.endDate, + _lastRange.startDate, widget.controller.view))) { + _pickerStateDetails.selectedRanges.removeLast(); + } + } + + void _drawYearCellSelection(dynamic selectedDate) { + switch (widget.picker.selectionMode) { + case DateRangePickerSelectionMode.single: + _drawSingleSelectionForYear(selectedDate); + break; + case DateRangePickerSelectionMode.multiple: + _drawMultipleSelectionForYear(selectedDate); + break; + case DateRangePickerSelectionMode.range: + _drawRangeSelectionForYear(selectedDate); + break; + case DateRangePickerSelectionMode.multiRange: + _drawRangesSelectionForYear(selectedDate); + } + } + + void _handleYearPanelSelection(Offset details) { + final int _selectedIndex = _getYearViewIndex(details.dx, details.dy); + final int viewCount = widget.picker.enableMultiView ? 2 : 1; + if (_selectedIndex == -1 || _selectedIndex >= 12 * viewCount) { + return; + } + + final dynamic date = widget.visibleDates[_selectedIndex]; + widget.getPickerStateDetails(_pickerStateDetails); + if (!widget.picker.allowViewNavigation) { + if (!DateRangePickerHelper.isBetweenMinMaxDateCell( + date, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + widget.controller.view, + widget.picker.isHijri)) { return; } - setState(() { - _currentDate = _controller.displayDate; - _updateCurrentVisibleDates(); - }); + _drawYearCellSelection(date); + widget.updatePickerStateDetails(_pickerStateDetails); + _yearView.selectionNotifier.value = !_yearView.selectionNotifier.value; + return; } - } - bool _checkDateWithInVisibleDates(DateTime date) { - switch (_controller.view) { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + switch (pickerView) { case DateRangePickerView.month: + break; + case DateRangePickerView.century: { - if (widget.monthViewSettings.numberOfWeeksInView != 6) { - return isDateWithInDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[_currentViewVisibleDates.length - 1], - date); - } else { - final DateTime currentMonth = _currentViewVisibleDates[ - _currentViewVisibleDates.length ~/ - (widget.enableMultiView ? 4 : 2)]; - return date.month == currentMonth.month && - date.year == currentMonth.year; + final int year = date.year ~/ 10; + final int minYear = widget.picker.minDate.year ~/ 10; + final int maxYear = widget.picker.maxDate.year ~/ 10; + if (year < minYear || year > maxYear) { + return; } + + _pickerStateDetails.view = DateRangePickerView.decade; } break; - case DateRangePickerView.year: - { - final int currentYear = _currentViewVisibleDates[0].year; - return currentYear == date.year; - } case DateRangePickerView.decade: { - final int minYear = _currentViewVisibleDates[0].year; - final int maxYear = _currentViewVisibleDates[10].year - 1; final int year = date.year; - return minYear <= year && maxYear >= year; + final int minYear = widget.picker.minDate.year; + final int maxYear = widget.picker.maxDate.year; + + if (year < minYear || year > maxYear) { + return; + } + _pickerStateDetails.view = DateRangePickerView.year; } - case DateRangePickerView.century: + break; + case DateRangePickerView.year: { - final int minYear = _currentViewVisibleDates[0].year; - final int maxYear = _currentViewVisibleDates[10].year - 1; final int year = date.year; - return minYear <= year && maxYear >= year; + final int month = date.month; + final int minYear = widget.picker.minDate.year; + final int maxYear = widget.picker.maxDate.year; + final int minMonth = widget.picker.minDate.month; + final int maxMonth = widget.picker.maxDate.month; + + if ((year < minYear || (year == minYear && month < minMonth)) || + (year > maxYear || (year == maxYear && month > maxMonth))) { + return; + } + + _pickerStateDetails.view = DateRangePickerView.month; } } - return false; + _pickerStateDetails.currentDate = date; + widget.updatePickerStateDetails(_pickerStateDetails); } - void _updateCurrentVisibleDates() { - switch (_view) { - case DateRangePickerView.month: - { - _currentViewVisibleDates = getVisibleDates( - _currentDate, - null, - widget.monthViewSettings.firstDayOfWeek, - _getViewDatesCount( - _view, widget.monthViewSettings.numberOfWeeksInView)); - } - break; - case DateRangePickerView.year: - case DateRangePickerView.decade: - case DateRangePickerView.century: - { - _currentViewVisibleDates = _getVisibleYearDates(_currentDate, _view); - } + void _drawSingleSelectionForMonth(dynamic selectedDate) { + if (widget.picker.toggleDaySelection && + isSameDate(selectedDate, _pickerStateDetails.selectedDate)) { + selectedDate = null; } + + _pickerStateDetails.selectedDate = selectedDate; } - DateTime _updateCurrentDate(SfDateRangePicker oldWidget) { - if (oldWidget.controller == widget.controller && - widget.controller != null && - oldWidget.controller.view == DateRangePickerView.month && - _controller.view != DateRangePickerView.month) { - return _currentViewVisibleDates[ - _currentViewVisibleDates.length ~/ (widget.enableMultiView ? 4 : 2)]; + void _drawMultipleSelectionForMonth(dynamic selectedDate) { + final int selectedIndex = DateRangePickerHelper.isDateIndexInCollection( + _pickerStateDetails.selectedDates, selectedDate); + if (selectedIndex == -1) { + _pickerStateDetails.selectedDates ??= []; + _pickerStateDetails.selectedDates.add(selectedDate); + } else { + _pickerStateDetails.selectedDates.removeAt(selectedIndex); } - - return _currentViewVisibleDates[0]; } - Widget _addChildren(double top, double height, double width) { - _headerVisibleDates.value = _currentViewVisibleDates; - return Stack(children: [ - Positioned( - top: 0, - right: 0, - left: 0, - height: widget.headerHeight, - child: GestureDetector( - child: Container( - color: widget.headerStyle.backgroundColor ?? - _datePickerTheme.headerBackgroundColor, - child: _PickerHeaderView( - _headerVisibleDates, - widget.headerStyle, - widget.selectionMode, - _view, - widget.monthViewSettings.numberOfWeeksInView, - widget.showNavigationArrow, - widget.navigationDirection, - widget.monthViewSettings.enableSwipeSelection, - widget.minDate, - widget.maxDate, - widget.monthFormat, - _datePickerTheme, - _locale, - width, - widget.headerHeight, - widget.allowViewNavigation, - _moveToPreviousView, - _moveToNextView, - widget.enableMultiView, - widget.viewSpacing, - widget.selectionColor ?? _datePickerTheme.selectionColor, - _isRtl, - _textScaleFactor), - height: widget.headerHeight, - ), - onTapUp: (TapUpDetails details) { - _updateCalendarTapCallbackForHeader(); - }, - ), - ), - _getViewHeaderView(), - Positioned( - top: top, - left: 0, - right: 0, - height: height, - child: _PickerScrollView( - widget, - _controller, - width, - height, - _isRtl, - _datePickerTheme, - _locale, - _textScaleFactor, - getPickerStateValues: (_PickerStateArgs details) { - _getPickerStateValues(details); - }, - updatePickerStateValues: (_PickerStateArgs details) { - _updatePickerStateValues(details); - }, - key: _scrollViewKey, - ), - ), - ]); + void _drawRangeSelectionForMonth(dynamic selectedDate) { + if (_pickerStateDetails.selectedRange != null && + _pickerStateDetails.selectedRange.startDate != null && + (_pickerStateDetails.selectedRange.endDate == null || + isSameDate(_pickerStateDetails.selectedRange.startDate, + _pickerStateDetails.selectedRange.endDate))) { + dynamic startDate = _pickerStateDetails.selectedRange.startDate; + dynamic endDate = selectedDate; + if (startDate.isAfter(endDate)) { + final dynamic temp = startDate; + startDate = endDate; + endDate = temp; + } + + _pickerStateDetails.selectedRange = widget.picker.isHijri + ? HijriDateRange(startDate, endDate) + : PickerDateRange(startDate, endDate); + } else { + _pickerStateDetails.selectedRange = widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); + } } - Widget _getViewHeaderView() { - if (_view == DateRangePickerView.month && - widget.navigationDirection == - DateRangePickerNavigationDirection.vertical) { - final Color todayTextColor = - widget.monthCellStyle.todayTextStyle != null && - widget.monthCellStyle.todayTextStyle.color != null - ? widget.monthCellStyle.todayTextStyle.color - : (widget.todayHighlightColor != null && - widget.todayHighlightColor != Colors.transparent - ? widget.todayHighlightColor - : _datePickerTheme.todayHighlightColor); - return Positioned( - left: 0, - top: widget.headerHeight, - right: 0, - height: widget.monthViewSettings.viewHeaderHeight, - child: Container( - color: widget.monthViewSettings.viewHeaderStyle.backgroundColor ?? - _datePickerTheme.viewHeaderBackgroundColor, - child: RepaintBoundary( - child: CustomPaint( - painter: _PickerViewHeaderPainter( - _currentViewVisibleDates, - widget.monthViewSettings.viewHeaderStyle, - widget.monthViewSettings.viewHeaderHeight, - widget.monthViewSettings, - _datePickerTheme, - _locale, - _isRtl, - widget.monthCellStyle, - widget.enableMultiView, - widget.viewSpacing, - todayTextColor, - _textScaleFactor), - ), - ), - ), - ); + void _drawRangesSelectionForMonth(dynamic selectedDate) { + _pickerStateDetails.selectedRanges ??= []; + int count = _pickerStateDetails.selectedRanges.length; + dynamic lastRange; + if (count > 0) { + lastRange = _pickerStateDetails.selectedRanges[count - 1]; } - return Positioned(left: 0, top: 0, right: 0, height: 0, child: Container()); - } + if (lastRange != null && + lastRange.startDate != null && + (lastRange.endDate == null || + isSameDate(lastRange.startDate, lastRange.endDate))) { + dynamic startDate = lastRange.startDate; + dynamic endDate = selectedDate; + if (startDate.isAfter(endDate)) { + final dynamic temp = startDate; + startDate = endDate; + endDate = temp; + } - void _moveToNextView() { - if (!_canMoveToNextView(_view, widget.monthViewSettings.numberOfWeeksInView, - widget.maxDate, _currentViewVisibleDates, widget.enableMultiView)) { - return; + 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)); } - _isRtl - ? _scrollViewKey.currentState._moveToPreviousViewWithAnimation() - : _scrollViewKey.currentState._moveToNextViewWithAnimation(); + count = _pickerStateDetails.selectedRanges.length; + _removeInterceptRanges( + _pickerStateDetails.selectedRanges, + _pickerStateDetails + .selectedRanges[_pickerStateDetails.selectedRanges.length - 1]); + lastRange = _pickerStateDetails + .selectedRanges[_pickerStateDetails.selectedRanges.length - 1]; + if (count != _pickerStateDetails.selectedRanges.length && + (lastRange.endDate == null || + isSameDate(lastRange.endDate, lastRange.startDate))) { + _pickerStateDetails.selectedRanges.removeLast(); + } } - void _moveToPreviousView() { - if (!_canMoveToPreviousView( - _view, - widget.monthViewSettings.numberOfWeeksInView, - widget.minDate, - _currentViewVisibleDates, - widget.enableMultiView)) { - return; + int _removeInterceptRangesForMonth(dynamic range, dynamic startDate, + dynamic endDate, int i, dynamic selectedRangeValue) { + if (range != null && + !DateRangePickerHelper.isRangeEquals(range, selectedRangeValue) && + ((range.startDate != null && + ((startDate != null && + isSameDate(range.startDate, startDate)) || + (endDate != null && + isSameDate(range.startDate, endDate)))) || + (range.endDate != null && + ((startDate != null && isSameDate(range.endDate, startDate)) || + (endDate != null && isSameDate(range.endDate, endDate)))) || + (range.startDate != null && + range.endDate != null && + ((startDate != null && + isDateWithInDateRange( + range.startDate, range.endDate, startDate)) || + (endDate != null && + isDateWithInDateRange( + range.startDate, range.endDate, endDate)))) || + (startDate != null && + endDate != null && + ((range.startDate != null && + isDateWithInDateRange( + startDate, endDate, range.startDate)) || + (range.endDate != null && + isDateWithInDateRange( + startDate, endDate, range.endDate)))) || + (range.startDate != null && + range.endDate != null && + startDate != null && + endDate != null && + ((range.startDate.isAfter(startDate) && + range.endDate.isBefore(endDate)) || + (range.endDate.isAfter(startDate) && + range.startDate.isBefore(endDate)))))) { + return i; } - _isRtl - ? _scrollViewKey.currentState._moveToNextViewWithAnimation() - : _scrollViewKey.currentState._moveToPreviousViewWithAnimation(); + return null; } - void _getPickerStateValues(_PickerStateArgs details) { - details._currentDate = _currentDate; - details._selectedDate = _selectedDate; - details._selectedDates = _selectedDates; - details._selectedRange = _selectedRange; - details._selectedRanges = _selectedRanges; - details._view = _view; + int _removeInterceptRangesForYear(dynamic range, dynamic startDate, + dynamic endDate, int i, dynamic selectedRangeValue) { + if (range == null || + DateRangePickerHelper.isRangeEquals(range, selectedRangeValue)) { + return null; + } + + /// Check the range start date equal to start date or end date. + if (range.startDate != null && + ((startDate != null && + DateRangePickerHelper.isSameCellDates( + range.startDate, startDate, widget.controller.view)) || + (endDate != null && + DateRangePickerHelper.isSameCellDates( + range.startDate, endDate, widget.controller.view)))) { + return i; + } + + /// Check the range end date equal to start date or end date. + if (range.endDate != null && + ((startDate != null && + DateRangePickerHelper.isSameCellDates( + range.endDate, startDate, widget.controller.view)) || + (endDate != null && + DateRangePickerHelper.isSameCellDates( + range.endDate, endDate, widget.controller.view)))) { + return i; + } + + /// Check the start date or end date placed inside the range. + if (range.startDate != null && + range.endDate != null && + ((startDate != null && + _isDateWithInYearRange( + range.startDate, range.endDate, startDate)) || + (endDate != null && + _isDateWithInYearRange( + range.startDate, range.endDate, endDate)))) { + return i; + } + + /// Check the range start or end date placed in between the start + /// and end date. + if (startDate != null && + endDate != null && + ((range.startDate != null && + _isDateWithInYearRange(startDate, endDate, range.startDate)) || + (range.endDate != null && + _isDateWithInYearRange(startDate, endDate, range.endDate)))) { + return i; + } + + /// Check the range in between the start and end date or the start and end + /// date placed inside th range. + if (range.startDate != null && + range.endDate != null && + startDate != null && + endDate != null && + ((range.startDate.isAfter(startDate) && + range.endDate.isBefore(endDate)) || + (range.endDate.isAfter(startDate) && + range.startDate.isBefore(endDate)))) { + return i; + } + + return null; } - void _updatePickerStateValues(_PickerStateArgs details) { - if (details._currentDate != null) { - if (!isSameOrAfterDate(widget.minDate, details._currentDate)) { - details._currentDate = widget.minDate; - } + /// Check whether the date is in between the start and end date value. + bool _isDateWithInYearRange( + dynamic startDate, dynamic endDate, dynamic date) { + if (startDate == null || endDate == null || date == null) { + return false; + } - if (!isSameOrBeforeDate(widget.maxDate, details._currentDate)) { - details._currentDate = widget.maxDate; - } + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); - _currentDate = details._currentDate; - _controller.displayDate = _currentDate; + /// Check the start date as before of end date, if not then swap + /// the start and end date values. + if (startDate.isAfter(endDate)) { + final dynamic temp = startDate; + startDate = endDate; + endDate = temp; } - if (details._currentViewVisibleDates != null && - _currentViewVisibleDates != details._currentViewVisibleDates) { - _currentViewVisibleDates = details._currentViewVisibleDates; - _headerVisibleDates.value = _currentViewVisibleDates; - switch (_controller.view) { + /// Check the date is equal or after of the start date and + /// the date is equal or before of the end date. + if ((DateRangePickerHelper.isSameCellDates(endDate, date, pickerView) || + endDate.isAfter(date)) && + (DateRangePickerHelper.isSameCellDates(startDate, date, pickerView) || + startDate.isBefore(date))) { + return true; + } + + return false; + } + + void _removeInterceptRanges( + List selectedRanges, dynamic selectedRangeValue) { + if (selectedRanges == null || + selectedRanges.isEmpty || + selectedRangeValue == null) { + return; + } + + dynamic startDate = selectedRangeValue.startDate; + dynamic endDate = selectedRangeValue.endDate; + if (startDate != null && endDate != null && startDate.isAfter(endDate)) { + final dynamic temp = startDate; + startDate = endDate; + endDate = temp; + } + + final List interceptIndex = []; + for (int i = 0; i < selectedRanges.length; i++) { + final dynamic range = selectedRanges[i]; + //// The below condition validate the following scenarios + //// Check the range as not null and range is not a new selected range, + //// Check the range start date as equal with selected range start or end date + //// Check the range end date as equal with selected range start or end date + //// Check the selected start date placed in between range start or end date + //// Check the selected end date placed in between range start or end date + //// Check the selected range occupies the range. + int index; + switch (_pickerStateDetails.view) { case DateRangePickerView.month: { - if (!widget.monthViewSettings.showTrailingAndLeadingDates && - widget.monthViewSettings.numberOfWeeksInView == 6) { - final DateTime visibleDate = _currentViewVisibleDates[ - _currentViewVisibleDates.length ~/ - (widget.enableMultiView ? 4 : 2)]; - _raisePickerViewChangedCallback(widget, - visibleDateRange: PickerDateRange( - _getMonthStartDate(visibleDate), - widget.enableMultiView - ? _getMonthEndDate(_getNextViewStartDate( - _controller.view, 6, visibleDate, _isRtl)) - : _getMonthEndDate(visibleDate)), - view: _controller.view); - } else { - _raisePickerViewChangedCallback(widget, - visibleDateRange: PickerDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[ - _currentViewVisibleDates.length - 1]), - view: _controller.view); - } + index = _removeInterceptRangesForMonth( + range, startDate, endDate, i, selectedRangeValue); } break; case DateRangePickerView.year: case DateRangePickerView.decade: case DateRangePickerView.century: { - _raisePickerViewChangedCallback(widget, - visibleDateRange: PickerDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[ - _currentViewVisibleDates.length - 1]), - view: _controller.view); + index = _removeInterceptRangesForYear( + range, startDate, endDate, i, selectedRangeValue); } } - } - - if (details._view != null && _view != details._view) { - _controller.view = details._view; - } - - if (_view == DateRangePickerView.month || !widget.allowViewNavigation) { - switch (widget.selectionMode) { - case DateRangePickerSelectionMode.single: - { - _selectedDate = details._selectedDate; - _controller.selectedDate = _selectedDate; - } - break; - case DateRangePickerSelectionMode.multiple: - { - _selectedDates = details._selectedDates; - _controller.selectedDates = _selectedDates; - } - break; - case DateRangePickerSelectionMode.range: - { - _selectedRange = details._selectedRange; - _controller.selectedRange = _selectedRange; - } - break; - case DateRangePickerSelectionMode.multiRange: - { - _selectedRanges = details._selectedRanges; - _controller.selectedRanges = _selectedRanges; - } + if (index != null) { + interceptIndex.add(index); } } - } - - // method to update the calendar tapped call back for the header view - void _updateCalendarTapCallbackForHeader() { - if (_view == DateRangePickerView.century || !widget.allowViewNavigation) { - return; - } - if (_view == DateRangePickerView.month) { - _controller.view = DateRangePickerView.year; - } else { - if (_view == DateRangePickerView.year) { - _controller.view = DateRangePickerView.decade; - } else if (_view == DateRangePickerView.decade) { - _controller.view = DateRangePickerView.century; - } + interceptIndex.sort(); + for (int i = interceptIndex.length - 1; i >= 0; i--) { + selectedRanges.removeAt(interceptIndex[i]); } } } diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_settings.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker_manager.dart similarity index 70% rename from packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_settings.dart rename to packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker_manager.dart index f3df61480..0ebb89998 100644 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_settings.dart +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker_manager.dart @@ -1,4 +1,6 @@ -part of datepicker; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'picker_helper.dart'; /// Sets the style for customizing the [SfDateRangePicker] header view. /// @@ -353,6 +355,9 @@ class DateRangePickerMonthViewSettings { /// dates will be displayed even if the [showTrailingAndLeadingDates] property /// is set to [false]. /// + /// This property not applicable when the [SfDateRangePicker.pickerMode] set + /// as [DateRangePickerMode.hijri]. + /// /// See also: [DateRangePickerMonthCellStyle] to know about leading and /// trailing dates style. /// @@ -523,6 +528,9 @@ class DateRangePickerMonthViewSettings { /// /// Defaults to `false`. /// + /// _Note:_ This property not applicable when the + /// [SfDateRangePicker.pickerMode] set as [DateRangePickerMode.hijri]. + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -809,6 +817,9 @@ class DateRangePickerYearCellStyle { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// _Note:_ This property not applicable when the + /// [SfDateRangePicker.pickerMode] set as [DateRangePickerMode.hijri]. + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -971,6 +982,9 @@ class DateRangePickerYearCellStyle { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// _Note:_ This property not applicable when the + /// [SfDateRangePicker.pickerMode] set as [DateRangePickerMode.hijri]. + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1192,6 +1206,9 @@ class DateRangePickerMonthCellStyle { /// [DateRangePickerMonthViewSettings.showTrailingAndLeadingDates] property /// set as [true]. /// + /// This property not applicable when the [SfDateRangePicker.pickerMode] set + /// as [DateRangePickerMode.hijri]. + /// /// See also:[DateRangePickerMonthViewSettings.showTrailingAndLeadingDates]. /// /// ``` dart @@ -1231,6 +1248,9 @@ class DateRangePickerMonthCellStyle { /// [DateRangePickerMonthViewSettings.showTrailingAndLeadingDates] property /// set as [true]. /// + /// This property not applicable when the [SfDateRangePicker.pickerMode] set + /// as [DateRangePickerMode.hijri]. + /// /// See also:[DateRangePickerMonthViewSettings.showTrailingAndLeadingDates]. /// /// ``` dart @@ -1688,6 +1708,9 @@ class DateRangePickerMonthCellStyle { /// [DateRangePickerMonthViewSettings.showTrailingAndLeadingDates] property /// set as [true]. /// + /// This property not applicable when the [SfDateRangePicker.pickerMode] set + /// as [DateRangePickerMode.hijri]. + /// /// See also:[DateRangePickerMonthViewSettings.showTrailingAndLeadingDates]. /// /// ```dart @@ -1727,6 +1750,9 @@ class DateRangePickerMonthCellStyle { /// [DateRangePickerMonthViewSettings.showLeadingAndTrailingDate] property set /// as [true]. /// + /// This property not applicable when the [SfDateRangePicker.pickerMode] set + /// as [DateRangePickerMode.hijri]. + /// /// See also:[DateRangePickerMonthViewSettings.showLeadingAndTrailingDate]. /// /// ```dart @@ -1946,3 +1972,786 @@ class DateRangePickerMonthCellStyle { ]); } } + +typedef DateRangePickerValueChangedCallback = void Function(String); + +/// Notifier used to notify the when the objects properties changed. +class DateRangePickerValueChangeNotifier { + List _listeners; + + /// Calls the listener every time the controller's property changed. + /// + /// Listeners can be removed with [removePropertyChangedListener]. + void addPropertyChangedListener( + DateRangePickerValueChangedCallback listener) { + _listeners ??= []; + _listeners.add(listener); + } + + /// remove the listener used for notify the data source changes. + /// + /// Stop calling the listener every time in controller's property changed. + /// + /// If `listener` is not currently registered as a listener, this method does + /// nothing. + /// + /// Listeners can be added with [addPropertyChangedListener]. + void removePropertyChangedListener( + DateRangePickerValueChangedCallback listener) { + if (_listeners == null) { + return; + } + + _listeners.remove(listener); + } + + /// Call all the registered listeners. + /// + /// Call this method whenever the object changes, to notify any clients the + /// object may have. Listeners that are added during this iteration will not + /// be visited. Listeners that are removed during this iteration will not be + /// visited after they are removed. + /// + /// This method must not be called after [dispose] has been called. + /// + /// Surprising behavior can result when reentrantly removing a listener (i.e. + /// in response to a notification) that has been registered multiple times. + /// See the discussion at [removePropertyChangedListener]. + void notifyPropertyChangedListeners(String value) { + if (_listeners == null) { + return; + } + + for (final DateRangePickerValueChangedCallback listener in _listeners) { + if (listener != null) { + listener(value); + } + } + } + + /// Discards any resources used by the object. After this is called, the + /// object is not in a usable state and should be discarded (calls to + /// [addListener] and [removeListener] will throw after the object is + /// disposed). + /// + /// This method should only be called by the object's owner. + @mustCallSuper + void dispose() { + _listeners = null; + } +} + +/// An object that used for programmatic date navigation, date and range +/// selection and view switching in [SfDateRangePicker]. +/// +/// A [DateRangePickerController] served for several purposes. It can be used +/// to selected dates and ranges programmatically on [SfDateRangePicker] by +/// using the[controller.selectedDate], [controller.selectedDates], +/// [controller.selectedRange], [controller.selectedRanges]. It can be used to +/// change the [SfDateRangePicker] view by using the [controller.view] property. +/// It can be used to navigate to specific date by using the +/// [controller.displayDate] property. +/// +/// ## Listening to property changes: +/// The [DateRangePickerController] is a listenable. It notifies it's listeners +/// whenever any of attached [SfDateRangePicker]`s selected date, display date +/// and view changed (i.e: selecting a different date, swiping to next/previous +/// view and navigates to different view] in in [SfDateRangePicker]. +/// +/// ## Navigates to different view: +/// The [SfDateRangePicker] visible view can be changed by using the +/// [Controller.view] property, the property allow to change the view of +/// [SfDateRangePicker] programmatically on initial load and in rum time. +/// +/// ## Programmatic selection: +/// In [SfDateRangePicker] selecting dates programmatically can be achieved by +/// using the [controller.selectedDate], [controller.selectedDates], +/// [controller.selectedRange], [controller.selectedRanges] which allows to +/// select the dates or ranges programmatically on [SfDateRangePicker] on +/// initial load and in run time. +/// +/// See also: [DateRangePickerSelectionMode] +/// +/// Defaults to null. +/// +/// This example demonstrates how to use the [SfDateRangePickerController] for +/// [SfDateRangePicker]. +/// +/// ``` dart +/// +///class MyApp extends StatefulWidget { +/// @override +/// MyAppState createState() => MyAppState(); +///} +/// +///class MyAppState extends State { +/// DateRangePickerController _pickerController; +/// +/// @override +/// void initState() { +/// _pickerController = DateRangePickerController(); +/// _pickerController.selectedDates = [ +/// DateTime.now().add(Duration(days: 2)), +/// DateTime.now().add(Duration(days: 4)), +/// DateTime.now().add(Duration(days: 7)), +/// DateTime.now().add(Duration(days: 11)) +/// ]; +/// _pickerController.displayDate = DateTime.now(); +/// _pickerController.addPropertyChangedListener(handlePropertyChange); +/// super.initState(); +/// } +/// +/// void handlePropertyChange(String propertyName) { +/// if (propertyName == 'selectedDates') { +/// final List selectedDates = _pickerController.selectedDates; +/// } else if (propertyName == 'displayDate') { +/// final DateTime displayDate = _pickerController.displayDate; +/// } +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// home: Scaffold( +/// body: SfDateRangePicker( +/// view: DateRangePickerView.month, +/// controller: _pickerController, +/// selectionMode: DateRangePickerSelectionMode.multiple, +/// ), +/// ), +/// ); +/// } +///} +/// +/// ``` +class DateRangePickerController extends DateRangePickerValueChangeNotifier { + DateTime _selectedDate; + List _selectedDates; + PickerDateRange _selectedRange; + List _selectedRanges; + DateTime _displayDate; + DateRangePickerView _view; + + /// The selected date in the [SfDateRangePicker]. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.single] for other selection modes this + /// property will return as null. + DateTime get selectedDate => _selectedDate; + + /// Selects the given date programmatically in the [SfDateRangePicker] by + /// checking that the date falls in between the minimum and maximum date + /// range. + /// + /// _Note:_ If any date selected previously, will be removed and the selection + /// will be drawn to the date given in this property. + /// + /// If it is not [null] the widget will render the date selection for the date + /// set to this property, even the [SfDateRangePicker.initialSelectedDate] is + /// not null. + /// + /// It is only applicable when the [DateRangePickerSelectionMode] set as + /// [DateRangePickerSelectionMode.single]. + /// + /// ``` dart + /// + /// class MyAppState extends State { + /// DateRangePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = DateRangePickerController(); + /// _pickerController.selectedDate = DateTime.now().add((Duration( + /// days: 4))); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfDateRangePicker( + /// controller: _pickerController, + /// view: DateRangePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.single, + /// showNavigationArrow: true, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + set selectedDate(DateTime date) { + if (isSameDate(_selectedDate, date)) { + return; + } + + _selectedDate = date; + notifyPropertyChangedListeners('selectedDate'); + } + + /// The list of dates selected in the [SfDateRangePicker]. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.multiple] for other selection modes this + /// property will return as null. + List get selectedDates => _selectedDates; + + /// Selects the given dates programmatically in the [SfDateRangePicker] by + /// checking that the dates falls in between the minimum and maximum date + /// range. + /// + /// _Note:_ If any list of dates selected previously, will be removed and the + /// selection will be drawn to the dates set to this property. + /// + /// If it is not [null] the widget will render the date selection for the + /// dates set to this property, even the + /// [SfDateRangePicker.initialSelectedDates] is not null. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.multiple]. + /// + /// ``` dart + /// + /// class MyAppState extends State { + /// DateRangePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = DateRangePickerController(); + /// _pickerController.selectedDates = [ + /// DateTime.now().add((Duration(days: 4))), + /// DateTime.now().add((Duration(days: 7))), + /// DateTime.now().add((Duration(days: 8))) + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfDateRangePicker( + /// controller: _pickerController, + /// view: DateRangePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.multiple, + /// showNavigationArrow: true, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + set selectedDates(List dates) { + if (DateRangePickerHelper.isDateCollectionEquals(_selectedDates, dates)) { + return; + } + + _selectedDates = DateRangePickerHelper.cloneList(dates); + notifyPropertyChangedListeners('selectedDates'); + } + + /// selected date range in the [SfDateRangePicker]. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.range] for other selection modes this + /// property will return as null. + PickerDateRange get selectedRange => _selectedRange; + + /// Selects the given date range programmatically in the [SfDateRangePicker] + /// by checking that the range of dates falls in between the minimum and + /// maximum date range. + /// + /// _Note:_ If any date range selected previously, will be removed and the + /// selection will be drawn to the range of dates set to this property. + /// + /// If it is not [null] the widget will render the date selection for the + /// range set to this property, even the + /// [SfDateRangePicker.initialSelectedRange] is not null. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.range]. + /// + /// ``` dart + /// + /// class MyAppState extends State { + /// DateRangePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = DateRangePickerController(); + /// _pickerController.selectedRange = PickerDateRange( + /// DateTime.now().add(Duration(days: 4)), + /// DateTime.now().add(Duration(days: 5))); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfDateRangePicker( + /// controller: _pickerController, + /// view: DateRangePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + set selectedRange(PickerDateRange range) { + if (DateRangePickerHelper.isRangeEquals(_selectedRange, range)) { + return; + } + + _selectedRange = range; + notifyPropertyChangedListeners('selectedRange'); + } + + /// List of selected ranges in the [SfDateRangePicker]. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.multiRange] for other selection modes this + /// property will return as null. + List get selectedRanges => _selectedRanges; + + /// Selects the given date ranges programmatically in the [SfDateRangePicker] + /// by checking that the ranges of dates falls in between the minimum and + /// maximum date range. + /// + /// If it is not [null] the widget will render the date selection for the + /// ranges set to this property, even the + /// [SfDateRangePicker.initialSelectedRanges] is not null. + /// + /// _Note:_ If any date ranges selected previously, will be removed and the + /// selection will be drawn to the ranges of dates set to this property. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.multiRange]. + /// + /// ``` dart + /// + /// class MyAppState extends State { + /// DateRangePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = DateRangePickerController(); + /// _pickerController.selectedRanges = [ + /// PickerDateRange(DateTime.now().subtract(Duration(days: 4)), + /// DateTime.now().add(Duration(days: 4))), + /// PickerDateRange(DateTime.now().add(Duration(days: 11)), + /// DateTime.now().add(Duration(days: 16))) + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfDateRangePicker( + /// controller: _pickerController, + /// view: DateRangePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + set selectedRanges(List ranges) { + if (DateRangePickerHelper.isDateRangesEquals(_selectedRanges, ranges)) { + return; + } + + _selectedRanges = DateRangePickerHelper.cloneList(ranges); + notifyPropertyChangedListeners('selectedRanges'); + } + + /// The first date of the current visible view month, when the + /// [MonthViewSettings.numberOfWeeksInView] set with default value 6. + /// + /// If the [MonthViewSettings.numberOfWeeksInView] property set with value + /// other then 6, this will return the first visible date of the current + /// month. + DateTime get displayDate => _displayDate; + + /// Navigates to the given date programmatically without any animation in the + /// [SfDateRangePicker] by checking that the date falls in between the + /// [SfDateRangePicker.minDate] and [SfDateRangePicker.maxDate] date range. + /// + /// If the date falls beyond the [SfDateRangePicker.minDate] and + /// [SfDateRangePicker.maxDate] the widget will move the widgets min or max + /// date. + /// + /// + /// ``` dart + /// + /// class MyAppState extends State { + /// DateRangePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = DateRangePickerController(); + /// _pickerController.displayDate = DateTime(2022, 02, 05); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfDateRangePicker( + /// controller: _pickerController, + /// view: DateRangePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.single, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + set displayDate(DateTime date) { + if (isSameDate(_displayDate, date)) { + return; + } + + _displayDate = date; + notifyPropertyChangedListeners('displayDate'); + } + + /// The current visible [DateRangePickerView] of [SfDateRangePicker]. + DateRangePickerView get view => _view; + + /// Set the [SfDateRangePickerView] for the [SfDateRangePicker]. + /// + /// + /// The [SfDateRangePicker] will display the view sets to this property. + /// + /// ```dart + /// + /// class MyAppState extends State { + /// DateRangePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = DateRangePickerController(); + /// _pickerController.view = DateRangePickerView.year; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfDateRangePicker( + /// controller: _pickerController, + /// view: DateRangePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.single, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + set view(DateRangePickerView value) { + if (_view == value) { + return; + } + + _view = value; + notifyPropertyChangedListeners('view'); + } + + /// Moves to the next view programmatically with animation by checking that + /// the next view dates falls between the minimum and maximum date range. + /// + /// _Note:_ If the current view has the maximum date range, it will not move + /// to the next view. + /// + /// ```dart + /// + /// class MyApp extends StatefulWidget { + /// @override + /// MyAppState createState() => MyAppState(); + ///} + /// + ///class MyAppState extends State { + /// DateRangePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = DateRangePickerController(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// actions: [ + /// IconButton( + /// icon: Icon(Icons.arrow_forward), + /// onPressed: () { + /// _pickerController.forward(); + /// }, + /// ) + /// ], + /// title: Text('Date Range Picker Demo'), + /// leading: IconButton( + /// icon: Icon(Icons.arrow_back), + /// onPressed: () { + /// _pickerController.backward(); + /// }, + /// ), + /// ), + /// body: SfDateRangePicker( + /// controller: _pickerController, + /// view: DateRangePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.single, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + VoidCallback forward; + + /// Moves to the previous view programmatically with animation by checking + /// that the previous view dates falls between the minimum and maximum date + /// range. + /// + /// _Note:_ If the current view has the minimum date range, it will not move + /// to the previous view. + /// + /// ```dart + /// + /// class MyApp extends StatefulWidget { + /// @override + /// MyAppState createState() => MyAppState(); + ///} + /// + ///class MyAppState extends State { + /// DateRangePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = DateRangePickerController(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// actions: [ + /// IconButton( + /// icon: Icon(Icons.arrow_forward), + /// onPressed: () { + /// _pickerController.forward(); + /// }, + /// ) + /// ], + /// title: Text('Date Range Picker Demo'), + /// leading: IconButton( + /// icon: Icon(Icons.arrow_back), + /// onPressed: () { + /// _pickerController.backward(); + /// }, + /// ), + /// ), + /// body: SfDateRangePicker( + /// controller: _pickerController, + /// view: DateRangePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.single, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + VoidCallback backward; +} + +/// Selection modes for [SfDateRangePicker]. +enum DateRangePickerSelectionMode { + /// - DateRangePickerSelectionMode.single, Allows to select a single date, + /// selecting a new date will remove the selection for previous date and + /// updates selection to the new selected date. + single, + + /// - DateRangePickerSelection.multiple, Allows multiple date selection, + /// selecting a new date will not remove the selection for previous dates, + /// allows to select as many dates as possible. + multiple, + + /// - DateRangePickerSelection.range, Allows to select a single range of + /// dates. + /// See also: [PickerDateRange] + range, + + /// - DateRangePickerSelection.multiRange, Allows to select a multiple ranges + /// of dates. + /// + /// See also: [PickerDateRange]. + multiRange +} + +/// Available views for [SfDateRangePicker]. +enum DateRangePickerView { + /// - DateRangePickerView.month, Displays the month view. + month, + + /// - DateRangePickerView.year, Displays the year view. + year, + + /// - DateRangePickerView.decade, Displays the decade view. + decade, + + /// - DateRangePickerView.century, Display the century view. + /// + /// _Note:_ This property not applicable when the + /// [SfDateRangePicker.pickerMode] set as [DateRangePickerMode.hijri]. + century +} + +/// The shape for the selection view in [SfDateRangePicker]. +enum DateRangePickerSelectionShape { + /// - DateRangePickerSelectionShape.circle, Draws the date selection in circle + /// shape. + circle, + + /// - DateRangePickerSelectionShape.rectangle, Draws the date selection in + /// rectangle shape. + rectangle +} + +/// A direction in which the [SfDateRangePicker] navigates. +enum DateRangePickerNavigationDirection { + /// - DateRangePickerNavigationDirection.vertical, Navigates in top and bottom + /// direction. + vertical, + + /// - DateRangePickerNavigationDirection.horizontal, Navigates in right and + /// left direction. + horizontal +} + +/// args to update the required properties from picker state to it's children's +class PickerStateArgs { + /// Holds the current view display date. + dynamic currentDate; + + /// Holds the current view visible dates. + List currentViewVisibleDates; + + /// Holds the current selected date. + dynamic selectedDate; + + /// Holds the current selected dates. + List selectedDates; + + /// Holds the current selected range. + dynamic selectedRange; + + /// Holds the current selected ranges. + List selectedRanges; + + /// Holds the current picker view. + DateRangePickerView view; +} + +/// The dates that visible on the view changes in [SfDateRangePicker]. +/// +/// Details for [DateRangePickerViewChangedCallback], such as [visibleDateRange] +/// and [view]. +@immutable +class DateRangePickerViewChangedArgs { + /// Creates details for [DateRangePickerViewChangedCallback]. + const DateRangePickerViewChangedArgs(this.visibleDateRange, this.view); + + /// The date range of the currently visible view dates. + /// + /// See also: [PickerDateRange]. + final PickerDateRange visibleDateRange; + + /// The currently visible [DateRangePickerView] in the [SfDateRangePicker]. + /// + /// See also: [DateRangePickerView]. + final DateRangePickerView view; +} + +/// The selected dates or ranges changes in the [SfDateRangePicker]. +/// +/// Details for [DateRangePickerSelectionChangedCallback], such as selected +/// value. +@immutable +class DateRangePickerSelectionChangedArgs { + /// Creates details for [DateRangePickerSelectionChangedCallback]. + const DateRangePickerSelectionChangedArgs(this.value); + + /// The changed selected dates or ranges value. + /// + /// The argument value will return the changed date as [DateTime] when the + /// widget [DateRangePickerSelectionMode] set as single. + /// + /// The argument value will return the changed dates as [List] when + /// the widget [DateRangePickerSelectionMode] set as multiple. + /// + /// The argument value will return the changed range as [PickerDateRange] + /// when the widget [DateRangePickerSelectionMode] set as range. + /// + /// The argument value will return the changed ranges as + /// [List visibleDates; +} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/header_view/picker_header_painter.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/header_view/picker_header_painter.dart deleted file mode 100644 index 965c2d6f2..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/header_view/picker_header_painter.dart +++ /dev/null @@ -1,236 +0,0 @@ -part of datepicker; - -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) - : super(repaint: visibleDates); - - final DateRangePickerHeaderStyle headerStyle; - final DateRangePickerView view; - final int numberOfWeeksInView; - final SfDateRangePickerThemeData datePickerTheme; - final bool isRtl; - final String monthFormat; - final bool hovering; - final bool enableMultiView; - final double multiViewSpacing; - final Color hoverColor; - final Locale locale; - final double textScaleFactor; - ValueNotifier> visibleDates; - String _headerText; - TextPainter _textPainter; - - @override - void paint(Canvas canvas, Size size) { - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - double xPosition = 0; - _textPainter = _textPainter ?? TextPainter(); - _textPainter.textDirection = TextDirection.ltr; - _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; - _textPainter.maxLines = 1; - - _headerText = ''; - final double width = - enableMultiView && headerStyle.textAlign == TextAlign.center - ? (size.width - multiViewSpacing) / 2 - : size.width; - final int count = - enableMultiView && headerStyle.textAlign == TextAlign.center ? 2 : 1; - for (int j = 0; j < count; j++) { - final int currentViewIndex = isRtl ? _getRtlIndex(count, j) : j; - xPosition = (currentViewIndex * width) + 10; - final String text = _getHeaderText(j); - _headerText += j == 1 ? ' ' + text : text; - TextStyle style = - headerStyle.textStyle ?? datePickerTheme.headerTextStyle; - if (hovering) { - style = style.copyWith(color: hoverColor); - } - - final TextSpan span = TextSpan(text: text, style: style); - _textPainter.text = span; - - if (headerStyle.textAlign == TextAlign.justify) { - _textPainter.textAlign = headerStyle.textAlign; - } - - _textPainter.layout( - minWidth: 0, - maxWidth: ((currentViewIndex + 1) * width) - xPosition > 0 - ? ((currentViewIndex + 1) * width) - xPosition - : 0); - - if (headerStyle.textAlign == TextAlign.center) { - xPosition = (currentViewIndex * width) + - (currentViewIndex * multiViewSpacing) + - (width / 2) - - (_textPainter.width / 2); - } else if ((!isRtl && - (headerStyle.textAlign == TextAlign.right || - headerStyle.textAlign == TextAlign.end)) || - (isRtl && - (headerStyle.textAlign == TextAlign.left || - headerStyle.textAlign == TextAlign.start))) { - xPosition = - ((currentViewIndex + 1) * width) - _textPainter.width - xPosition; - } - _textPainter.paint( - canvas, Offset(xPosition, size.height / 2 - _textPainter.height / 2)); - } - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _PickerHeaderPainter oldWidget = oldDelegate; - return oldWidget.headerStyle != headerStyle || - oldWidget.isRtl != isRtl || - oldWidget.numberOfWeeksInView != numberOfWeeksInView || - oldWidget.locale != locale || - oldWidget.datePickerTheme != datePickerTheme || - oldWidget.textScaleFactor != textScaleFactor || - (kIsWeb && - (oldWidget.hovering != hovering || - oldWidget.hoverColor != hoverColor)); - } - - String _getMonthHeaderText(int startIndex, int endIndex, List dates, - int middleIndex, int datesCount) { - if (numberOfWeeksInView != 6 && - dates[startIndex].month != dates[endIndex].month) { - final String monthTextFormat = - monthFormat == null || monthFormat.isEmpty ? 'MMM' : monthFormat; - int endIndex = dates.length - 1; - if (enableMultiView && headerStyle.textAlign == TextAlign.center) { - endIndex = endIndex; - } - - final String startText = DateFormat(monthTextFormat, locale.toString()) - .format(dates[startIndex]) - .toString() + - ' ' + - dates[startIndex].year.toString(); - final String endText = DateFormat(monthTextFormat, locale.toString()) - .format(dates[endIndex]) - .toString() + - ' ' + - dates[endIndex].year.toString(); - if (startText == endText) { - return startText; - } - - return startText + ' - ' + endText; - } else { - final String monthTextFormat = - monthFormat == null || monthFormat.isEmpty ? 'MMMM' : monthFormat; - final String text = DateFormat(monthTextFormat, locale.toString()) - .format(dates[middleIndex]) - .toString() + - ' ' + - dates[middleIndex].year.toString(); - if (enableMultiView && headerStyle.textAlign != TextAlign.center) { - return text + - ' - ' + - DateFormat(monthTextFormat, locale.toString()) - .format(dates[datesCount + middleIndex]) - .toString() + - ' ' + - dates[datesCount + middleIndex].year.toString(); - } - - return text; - } - } - - String _getHeaderText(int index) { - final List dates = visibleDates.value; - final int count = enableMultiView ? 2 : 1; - final int datesCount = dates.length ~/ count; - final int startIndex = index * datesCount; - final int endIndex = ((index + 1) * datesCount) - 1; - final int middleIndex = startIndex + (datesCount ~/ 2); - switch (view) { - case DateRangePickerView.month: - { - return _getMonthHeaderText( - startIndex, endIndex, dates, middleIndex, datesCount); - } - break; - case DateRangePickerView.year: - { - final DateTime date = dates[middleIndex]; - if (enableMultiView && headerStyle.textAlign != TextAlign.center) { - return date.year.toString() + - ' - ' + - dates[datesCount + middleIndex].year.toString(); - } - - return date.year.toString(); - } - case DateRangePickerView.decade: - { - final int year = (dates[middleIndex].year ~/ 10) * 10; - if (enableMultiView && headerStyle.textAlign != TextAlign.center) { - return year.toString() + - ' - ' + - (((dates[datesCount + middleIndex].year ~/ 10) * 10) + 9) - .toString(); - } - - return year.toString() + ' - ' + (year + 9).toString(); - } - case DateRangePickerView.century: - { - final int year = (dates[middleIndex].year ~/ 100) * 100; - if (enableMultiView && headerStyle.textAlign != TextAlign.center) { - return year.toString() + - ' - ' + - (((dates[datesCount + middleIndex].year ~/ 100) * 100) + 99) - .toString(); - } - - return year.toString() + ' - ' + (year + 99).toString(); - } - } - - return ''; - } - - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the list - /// of custom painter semantics which contains the rect area and the semantics - /// properties for accessibility - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - final Rect rect = Offset.zero & size; - return [ - CustomPainterSemantics( - rect: rect, - properties: SemanticsProperties( - label: _headerText != null ? _headerText.replaceAll('-', 'to') : '', - textDirection: TextDirection.ltr, - ), - ), - ]; - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - return true; - } -} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/header_view/picker_header_view.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/header_view/picker_header_view.dart deleted file mode 100644 index e34526123..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/header_view/picker_header_view.dart +++ /dev/null @@ -1,292 +0,0 @@ -part of datepicker; - -@immutable -class _PickerHeaderView extends StatefulWidget { - const _PickerHeaderView( - this.visibleDates, - this.headerStyle, - this.selectionMode, - this.view, - this.numberOfWeeksInView, - this.showNavigationArrow, - this.navigationDirection, - this.enableSwipeSelection, - 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, - {Key key}) - : super(key: key); - - final double textScaleFactor; - final DateRangePickerSelectionMode selectionMode; - final DateRangePickerHeaderStyle headerStyle; - final DateRangePickerView view; - final int numberOfWeeksInView; - final bool showNavigationArrow; - final DateRangePickerNavigationDirection navigationDirection; - final DateTime minDate; - final DateTime maxDate; - final String monthFormat; - final bool enableSwipeSelection; - final bool allowViewNavigation; - final SfDateRangePickerThemeData datePickerTheme; - final Locale locale; - final ValueNotifier> visibleDates; - final VoidCallback previousNavigationCallback; - final VoidCallback nextNavigationCallback; - final double width; - final double height; - final bool isRtl; - final Color hoverColor; - final bool enableMultiView; - final double multiViewSpacing; - - @override - _PickerHeaderViewState createState() => _PickerHeaderViewState(); -} - -class _PickerHeaderViewState extends State<_PickerHeaderView> { - bool _hovering; - - @override - void initState() { - _hovering = false; - _addListener(); - super.initState(); - } - - @override - void didUpdateWidget(_PickerHeaderView oldWidget) { - widget.visibleDates.removeListener(_listener); - _addListener(); - super.didUpdateWidget(oldWidget); - } - - @override - Widget build(BuildContext context) { - double arrowWidth = 0; - double headerWidth = widget.width; - if (widget.showNavigationArrow || - ((widget.view == DateRangePickerView.month || - !widget.allowViewNavigation) && - widget.enableSwipeSelection && - (widget.selectionMode == DateRangePickerSelectionMode.range || - widget.selectionMode == - DateRangePickerSelectionMode.multiRange))) { - arrowWidth = widget.width / 6; - arrowWidth = arrowWidth > 50 ? 50 : arrowWidth; - headerWidth = widget.width - (arrowWidth * 2); - } - - Color arrowColor = widget.headerStyle.textStyle != null - ? widget.headerStyle.textStyle.color - : (widget.datePickerTheme.headerTextStyle.color); - arrowColor = arrowColor.withOpacity(arrowColor.opacity * 0.6); - Color prevArrowColor = arrowColor; - Color nextArrowColor = arrowColor; - final List dates = widget.visibleDates.value; - if (!_canMoveToNextView(widget.view, widget.numberOfWeeksInView, - widget.maxDate, dates, widget.enableMultiView)) { - nextArrowColor = nextArrowColor.withOpacity(arrowColor.opacity * 0.5); - } - - if (!_canMoveToPreviousView(widget.view, widget.numberOfWeeksInView, - widget.minDate, dates, widget.enableMultiView)) { - prevArrowColor = prevArrowColor.withOpacity(arrowColor.opacity * 0.5); - } - - final Widget headerText = _getHeaderText(headerWidth); - - double arrowSize = widget.height * 0.5; - arrowSize = arrowSize > 25 ? 25 : arrowSize; - arrowSize = arrowSize * widget.textScaleFactor; - final Container leftArrow = - _getLeftArrow(arrowWidth, arrowColor, prevArrowColor, arrowSize); - - final Container rightArrow = - _getRightArrow(arrowWidth, arrowColor, nextArrowColor, arrowSize); - - if (widget.headerStyle.textAlign == TextAlign.left || - widget.headerStyle.textAlign == TextAlign.start) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.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, - ]); - } else { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - leftArrow, - headerText, - rightArrow, - ]); - } - } - - @override - void dispose() { - widget.visibleDates.removeListener(_listener); - super.dispose(); - } - - void _listener() { - if (!mounted) { - return; - } - - if (widget.showNavigationArrow || - ((widget.view == DateRangePickerView.month || - !widget.allowViewNavigation) && - widget.enableSwipeSelection && - (widget.selectionMode == DateRangePickerSelectionMode.range || - widget.selectionMode == - DateRangePickerSelectionMode.multiRange))) { - setState(() {/*Updates the header when visible dates changes */}); - } - } - - void _addListener() { - SchedulerBinding.instance.addPostFrameCallback((_) { - widget.visibleDates.addListener(_listener); - }); - } - - Widget _getHeaderText(double headerWidth) { - return MouseRegion( - onEnter: (PointerEnterEvent event) { - if (widget.view == DateRangePickerView.century) { - return; - } - - setState(() { - _hovering = true; - }); - }, - onHover: (PointerHoverEvent event) { - if (widget.view == DateRangePickerView.century) { - return; - } - - setState(() { - _hovering = true; - }); - }, - onExit: (PointerExitEvent event) { - setState(() { - _hovering = false; - }); - }, - child: RepaintBoundary( - child: CustomPaint( - // Returns the header view as a child for the calendar. - 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), - size: Size(headerWidth, widget.height), - ))); - } - - Widget _getLeftArrow(double arrowWidth, Color arrowColor, - Color prevArrowColor, double arrowSize) { - return Container( - alignment: Alignment.center, - color: widget.headerStyle.backgroundColor ?? - widget.datePickerTheme.headerBackgroundColor, - width: arrowWidth, - padding: const EdgeInsets.all(0), - child: FlatButton( - //// set splash color as transparent when arrow reaches min date(disabled) - splashColor: prevArrowColor != arrowColor ? Colors.transparent : null, - hoverColor: prevArrowColor != arrowColor ? Colors.transparent : null, - highlightColor: - prevArrowColor != arrowColor ? Colors.transparent : null, - color: widget.headerStyle.backgroundColor ?? - widget.datePickerTheme.headerBackgroundColor, - onPressed: widget.previousNavigationCallback, - padding: const EdgeInsets.all(0), - child: Semantics( - label: 'Backward', - child: Icon( - widget.navigationDirection == - DateRangePickerNavigationDirection.horizontal - ? Icons.chevron_left - : Icons.keyboard_arrow_up, - color: prevArrowColor, - size: arrowSize, - ), - ), - ), - ); - } - - Widget _getRightArrow(double arrowWidth, Color arrowColor, - Color nextArrowColor, double arrowSize) { - return Container( - alignment: Alignment.center, - color: widget.headerStyle.backgroundColor ?? - widget.datePickerTheme.headerBackgroundColor, - width: arrowWidth, - padding: const EdgeInsets.all(0), - child: FlatButton( - //// set splash color as transparent when arrow reaches max date(disabled) - splashColor: nextArrowColor != arrowColor ? Colors.transparent : null, - hoverColor: nextArrowColor != arrowColor ? Colors.transparent : null, - highlightColor: - nextArrowColor != arrowColor ? Colors.transparent : null, - color: widget.headerStyle.backgroundColor ?? - widget.datePickerTheme.headerBackgroundColor, - onPressed: widget.nextNavigationCallback, - padding: const EdgeInsets.all(0), - child: Semantics( - label: 'Forward', - child: Icon( - widget.navigationDirection == - DateRangePickerNavigationDirection.horizontal - ? Icons.chevron_right - : Icons.keyboard_arrow_down, - color: nextArrowColor, - size: arrowSize, - ), - ), - ), - ); - } -} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/header_view/picker_view_header.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/header_view/picker_view_header.dart deleted file mode 100644 index 8d41aff2e..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/header_view/picker_view_header.dart +++ /dev/null @@ -1,207 +0,0 @@ -part of datepicker; - -class _PickerViewHeaderPainter extends CustomPainter { - _PickerViewHeaderPainter( - this.visibleDates, - this.viewHeaderStyle, - this.viewHeaderHeight, - this.monthViewSettings, - this.datePickerTheme, - this.locale, - this.isRtl, - this.monthCellStyle, - this.enableMultiView, - this.multiViewSpacing, - this.todayHighlightColor, - this.textScaleFactor); - - final DateRangePickerViewHeaderStyle viewHeaderStyle; - final DateRangePickerMonthViewSettings monthViewSettings; - final List visibleDates; - final double viewHeaderHeight; - final DateRangePickerMonthCellStyle monthCellStyle; - final Locale locale; - final bool isRtl; - final Color todayHighlightColor; - final bool enableMultiView; - final double multiViewSpacing; - final SfDateRangePickerThemeData datePickerTheme; - final double textScaleFactor; - TextPainter _textPainter; - - @override - void paint(Canvas canvas, Size size) { - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - double width = size.width / _kNumberOfDaysInWeek; - if (enableMultiView) { - width = (size.width - multiViewSpacing) / (_kNumberOfDaysInWeek * 2); - } - - /// Initializes the default text style for the texts in view header of - /// calendar. - final TextStyle viewHeaderDayStyle = - viewHeaderStyle.textStyle ?? datePickerTheme.viewHeaderTextStyle; - final DateTime today = DateTime.now(); - TextStyle dayTextStyle = viewHeaderDayStyle; - double xPosition = 0; - double yPosition = 0; - final int count = enableMultiView ? 2 : 1; - final int datesCount = - enableMultiView ? visibleDates.length ~/ 2 : visibleDates.length; - for (int j = 0; j < count; j++) { - final int currentViewIndex = isRtl ? _getRtlIndex(count, j) : j; - DateTime currentDate; - final bool hasToday = monthViewSettings.numberOfWeeksInView > 0 && - monthViewSettings.numberOfWeeksInView < 6 - ? true - : (visibleDates[(currentViewIndex * datesCount) + (datesCount ~/ 2)] - .month == - today.month && - visibleDates[(currentViewIndex * datesCount) + - (datesCount ~/ 2)] - .year == - today.year) - ? true - : false; - for (int i = 0; i < _kNumberOfDaysInWeek; i++) { - int index = isRtl ? _getRtlIndex(_kNumberOfDaysInWeek, i) : i; - index = index + (currentViewIndex * datesCount); - currentDate = visibleDates[index]; - String dayText = - DateFormat(monthViewSettings.dayFormat, locale.toString()) - .format(currentDate) - .toString() - .toUpperCase(); - dayText = _updateViewHeaderFormat(dayText); - - if (hasToday && - currentDate.weekday == today.weekday && - isDateWithInDateRange( - visibleDates[(currentViewIndex * datesCount)], - visibleDates[((currentViewIndex + 1) * datesCount) - 1], - today)) { - final Color textColor = monthCellStyle.todayTextStyle != null && - monthCellStyle.todayTextStyle.color != null - ? monthCellStyle.todayTextStyle.color - : todayHighlightColor ?? datePickerTheme.todayHighlightColor; - dayTextStyle = viewHeaderDayStyle.copyWith(color: textColor); - } else { - dayTextStyle = viewHeaderDayStyle; - } - - final TextSpan dayTextSpan = TextSpan( - text: dayText, - style: dayTextStyle, - ); - - _textPainter = _textPainter ?? - TextPainter( - textDirection: TextDirection.ltr, - textAlign: TextAlign.left, - textScaleFactor: textScaleFactor, - textWidthBasis: TextWidthBasis.longestLine); - _textPainter.text = dayTextSpan; - _textPainter.layout(minWidth: 0, maxWidth: width); - yPosition = (viewHeaderHeight - _textPainter.height) / 2; - _textPainter.paint( - canvas, - Offset( - xPosition + (width / 2 - _textPainter.width / 2), yPosition)); - xPosition += width; - } - - xPosition += multiViewSpacing; - } - } - - String _updateViewHeaderFormat(String dayText) { - //// EE format value shows the week days as S, M, T, W, T, F, S. - /// For other languages showing the first letter of the weekday turns into - /// wrong meaning, hence we have shown the first letter of weekday when the - /// date farmat set as defautlt and the locale set as English. - /// - /// Eg: In chinesh the first letter or `Sunday` represents `Weekday`, hence - /// to avoid this added this condition based on locale. - if (monthViewSettings.dayFormat == 'EE' && - (locale == null || locale.languageCode == 'en')) { - dayText = dayText[0]; - } - - return dayText; - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _PickerViewHeaderPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.viewHeaderStyle != viewHeaderStyle || - oldWidget.viewHeaderHeight != viewHeaderHeight || - oldWidget.todayHighlightColor != todayHighlightColor || - oldWidget.monthViewSettings != monthViewSettings || - oldWidget.datePickerTheme != datePickerTheme || - oldWidget.isRtl != isRtl || - oldWidget.locale != locale || - oldWidget.textScaleFactor != textScaleFactor; - } - - List _getSemanticsBuilder(Size size) { - final List semanticsBuilder = - []; - double left, cellWidth; - cellWidth = size.width / _kNumberOfDaysInWeek; - int count = 1; - int datesCount = visibleDates.length; - if (enableMultiView) { - cellWidth = (size.width - multiViewSpacing) / 14; - count = 2; - datesCount = visibleDates.length ~/ 2; - } - - left = isRtl ? size.width - cellWidth : 0; - const double top = 0; - for (int j = 0; j < count; j++) { - for (int i = 0; i < _kNumberOfDaysInWeek; i++) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(left, top, cellWidth, size.height), - properties: SemanticsProperties( - label: DateFormat('EEEEE') - .format(visibleDates[(j * datesCount) + i]) - .toString() - .toUpperCase(), - textDirection: TextDirection.ltr, - ), - )); - if (isRtl) { - left -= cellWidth; - } else { - left += cellWidth; - } - } - - if (isRtl) { - left -= multiViewSpacing; - } else { - left += multiViewSpacing; - } - } - - return semanticsBuilder; - } - - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the list - /// of custom painter semantics which contains the rect area and the semantics - /// properties for accessibility - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilder(size); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _PickerViewHeaderPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates; - } -} 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 new file mode 100644 index 000000000..857db1853 --- /dev/null +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/hijri_date_picker_manager.dart @@ -0,0 +1,1715 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_datepicker/datepicker.dart'; +import 'picker_helper.dart'; + +/// Options to customize the month view of the [SfHijriDateRangePicker]. +/// +/// Allows to customize the [firstDayOfWeek], [dayFormat], [viewHeaderHeight]. +/// [viewHeaderStyle], [enableSwipeSelection], [blackoutDates], [specialDates] +/// and [weekendDays] in month view of date range picker. +/// +/// ```dart +/// +///Widget build(BuildContext context) { +/// return MaterialApp( +/// home: Scaffold( +/// body: SfHijriDateRangePicker( +/// view: HijriDatePickerView.month, +/// monthViewSettings: HijriDatePickerMonthViewSettings( +/// firstDayOfWeek: 1, +/// dayFormat: 'E', +/// viewHeaderHeight: 70, +/// viewHeaderStyle: HijriDatePickerViewHeaderStyle( +/// backgroundColor: Colors.blue, +/// textStyle: +/// TextStyle(fontWeight: FontWeight.w400, fontSize: 15, +/// Colors.black)), +/// enableSwipeSelection: false, +/// blackoutDates: [ +/// HijriDateTime.now().add(Duration(days: 4)) +/// ], +/// specialDates: [ +/// HijriDateTime.now().add(Duration(days: 7)), +/// HijriDateTime.now().add(Duration(days: 8)) +/// ], +/// weekendDays: [ +/// DateTime.monday, +/// DateTime.friday +/// ]), +/// ), +/// ), +/// ); +/// } +/// +/// ``` +@immutable +class HijriDatePickerMonthViewSettings { + /// Creates a date range picker month view settings for date range picker. + /// + /// The properties allows to customize the month view of + /// [SfHijriDateRangePicker]. + const HijriDatePickerMonthViewSettings( + {this.firstDayOfWeek = 7, + this.dayFormat = 'EE', + this.viewHeaderHeight = 30, + DateRangePickerViewHeaderStyle viewHeaderStyle, + this.enableSwipeSelection = true, + this.blackoutDates, + this.specialDates, + List weekendDays}) + : viewHeaderStyle = + viewHeaderStyle ?? const DateRangePickerViewHeaderStyle(), + weekendDays = weekendDays ?? const [6, 7]; + + /// Formats a text in the [SfHijriDateRangePicker] month view view header. + /// + /// Text format in the [SfHijriDateRangePicker] month view view header. + /// + /// Defaults to `EE`. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.single, + /// monthViewSettings: HijriDatePickerMonthViewSettings( + /// dayFormat: 'EEE'), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final String dayFormat; + + /// Enables the swipe selection for [SfHijriDateRangePicker], which allows to + /// select the range of dates by swiping on the dates. + /// + /// If it is [false] selecting a two different dates will form the range of + /// dates by covering the dates between the selected dates. + /// + /// Defaults to `true`. + /// + /// _Note:_ It is only applicable when the [DateRangePickerSelectionMode] + /// set as [DateRangePickerSelectionMode.range] or + /// [DateRangePickerSelectionMode.multiRange]. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// monthViewSettings: + /// HijriDatePickerMonthViewSettings( + /// enableSwipeSelection: false), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final bool enableSwipeSelection; + + /// The first day of the week in the [SfHijriDateRangePicker] month view. + /// + /// Allows you to change the first day of the week in the month view, + /// every month view will start from the day set to that property. + /// + /// Defaults to `7` which indicates `DateTime.sunday`. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// monthViewSettings: + /// HijriDatePickerMonthViewSettings(firstDayOfWeek: 2), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final int firstDayOfWeek; + + /// Sets the style to customize [SfHijriDateRangePicker] month view view + /// header. + /// + /// Allows to customize the [textStyle] and [backgroundColor] of the view + /// header view in month view of [SfHijriDateRangePicker]. + /// + /// See also: [DateRangePickerViewHeaderStyle]. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.single, + /// monthViewSettings: HijriDatePickerMonthViewSettings( + /// viewHeaderStyle: DateRangePickerViewHeaderStyle( + /// backgroundColor: Colors.red, + /// textStyle: TextStyle( + /// fontWeight: FontWeight.w500, + /// fontStyle: FontStyle.italic, + /// fontSize: 20, + /// color: Colors.white))), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final DateRangePickerViewHeaderStyle viewHeaderStyle; + + /// The height of the view header to the layout within this in month view of + /// [SfHijriDateRangePicker]. + /// + /// Defaults to `30`. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: HDateRangePickerSelectionMode.range, + /// monthViewSettings: + /// HijriDatePickerMonthViewSettings(viewHeaderHeight: 50), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final double viewHeaderHeight; + + /// Disables the interactions for certain dates in the month view of + /// [SfHijriDateRangePicker]. + /// + /// Defaults to null. + /// + /// Use [HijriDatePickerMonthCellStyle.blackoutDateTextStyle] or + /// [HijriDatePickerMonthCellStyle.blackoutDatesDecoration] property to + /// customize the appearance of blackout dates in month view. + /// + /// See also: + /// [HijriDatePickerMonthCellStyle.blackoutDateTextStyle] + /// [HijriDatePickerMonthCellStyle.blackoutDatesDecoration]. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// monthViewSettings: + /// HijriDatePickerMonthViewSettings( + /// blackoutDates: [ + /// HijriDateTime.now().add(Duration(days: 2)), + /// HijriDateTime.now().add(Duration(days: 3)), + /// HijriDateTime.now().add(Duration(days: 6)), + /// HijriDateTime.now().add(Duration(days: 7)) + /// ]), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final List blackoutDates; + + /// In the month view of [SfHijriDateRangePicker] highlights the unique dates + /// with different style rather than the other dates style. + /// + /// Defaults to null. + /// + /// Use [HijriDatePickerMonthCellStyle.specialDatesTextStyle] or + /// [HijriDatePickerMonthCellStyle.specialDatesDecoration] property to + /// customize the appearance of blackout dates in month view. + /// + /// See also: + /// [HijriDatePickerMonthCellStyle.specialDatesTextStyle] + /// [HijriDatePickerMonthCellStyle.specialDatesDecoration]. + /// + /// ```dart + /// + ///Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// monthViewSettings: + /// HijriDatePickerMonthViewSettings( + /// specialDates: [ + /// HijriDateTime.now().add(Duration(days: 2)), + /// HijriDateTime.now().add(Duration(days: 3)), + /// HijriDateTime.now().add(Duration(days: 6)), + /// HijriDateTime.now().add(Duration(days: 7)) + /// ]), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final List specialDates; + + /// The weekends for month view in [SfHijriDateRangePicker]. + /// + /// 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 + /// [HijriDatePickerMonthCellStyle.weekendDatesDecoration] property. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// monthViewSettings: HijriDatePickerMonthViewSettings( + /// weekendDays: [DateTime.friday, DateTime.saturday]), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final List weekendDays; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + final HijriDatePickerMonthViewSettings otherStyle = other; + return otherStyle.dayFormat == dayFormat && + otherStyle.firstDayOfWeek == firstDayOfWeek && + otherStyle.viewHeaderStyle == viewHeaderStyle && + otherStyle.viewHeaderHeight == viewHeaderHeight && + otherStyle.blackoutDates == blackoutDates && + otherStyle.specialDates == specialDates && + otherStyle.weekendDays == weekendDays && + otherStyle.enableSwipeSelection == enableSwipeSelection; + } + + @override + int get hashCode { + return hashValues( + dayFormat, + firstDayOfWeek, + viewHeaderStyle, + enableSwipeSelection, + viewHeaderHeight, + specialDates, + blackoutDates, + weekendDays); + } +} + +/// Options to customize the year and decade view of the +/// [SfHijriDateRangePicker]. +/// +/// Allows to customize the [textStyle], [todayTextStyle], +/// [disabledDatesTextStyle], [cellDecoration], [todayCellDecoration], and +/// [disabledDatesDecoration] in year and decade view of the +/// [SfHijriDateRangePicker]. +/// +/// ``` dart +/// +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// home: Scaffold( +/// body: SfHijriDateRangePicker( +/// view: HijriDatePickerView.decade, +/// enablePastDates: false, +/// yearCellStyle: HijriDateRangePickerYearCellStyle( +/// textStyle: TextStyle( +/// fontWeight: FontWeight.w400, fontSize: 15, +/// color: Colors.black), +/// todayTextStyle: TextStyle( +/// fontStyle: FontStyle.italic, +/// fontSize: 15, +/// fontWeight: FontWeight.w500, +/// color: Colors.red), +/// disabledDatesDecoration: BoxDecoration( +/// color: const Color(0xFFDFDFDF).withOpacity(0.2), +/// border: Border.all(color: const Color(0xFFB6B6B6), width: 1), +/// shape: BoxShape.circle), +/// ), +/// ), +/// ), +/// ); +/// } +/// +/// ``` +@immutable +class HijriDatePickerYearCellStyle { + /// Creates a date range picker year cell style for date range picker. + /// + /// 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}); + + /// The text style for the text in the [SfHijriDateRangePicker] year and + /// decade view cells. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.year, + /// selectionMode: DateRangePickerSelectionMode.range, + /// yearCellStyle: HijriDatePickerYearCellStyle( + /// textStyle: TextStyle( + /// fontSize: 14, + /// fontWeight: FontWeight.w400, + /// color: Colors.blue, + /// )), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final TextStyle textStyle; + + /// The text style for the text in the today cell of [SfHijriDateRangePicker] + /// year and decade view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.year, + /// selectionMode: DateRangePickerSelectionMode.range, + /// yearCellStyle: HijriDatePickerYearCellStyle( + /// todayTextStyle: TextStyle( + /// fontSize: 14, + /// fontWeight: FontWeight.w400, + /// color: Colors.red, + /// fontStyle: FontStyle.italic)), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final TextStyle todayTextStyle; + + /// The text style for the text in the disabled dates cell of + /// [SfHijriDateRangePicker] year and decade view. + /// + /// Here, disabled cells are the one which falls beyond the minimum and + /// maximum date range. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.year, + /// selectionMode: DateRangePickerSelectionMode.range, + /// yearCellStyle: HijriDatePickerYearCellStyle( + /// disabledDatesTextStyle: TextStyle( + /// fontSize: 12, + /// fontWeight: FontWeight.w300, + /// color: Colors.black)), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final TextStyle disabledDatesTextStyle; + + /// The decoration for the disabled cells of [SfHijriDateRangePicker] + /// year and decade view. + /// + /// Here, disabled cells are the one which falls beyond the minimum and + /// maximum date range. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.decade, + /// selectionMode: DateRangePickerSelectionMode.range, + /// yearCellStyle: HijriDatePickerYearCellStyle( + /// disabledDatesDecoration: BoxDecoration( + /// color: Colors.black.withOpacity(0.4), + /// border: Border.all(color: const Color(0xFF2B732F), + /// width: 1), + /// shape: BoxShape.rectangle), + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Decoration disabledDatesDecoration; + + /// The decoration for the cells of [SfHijriDateRangePicker] year and decade + /// view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.decade, + /// selectionMode: DateRangePickerSelectionMode.range, + /// yearCellStyle: HijriDatePickerYearCellStyle( + /// cellDecoration: BoxDecoration( + /// color: Colors.green, + /// border: Border.all(color: const Color(0xFF2B732F), + /// width: 1), + /// shape: BoxShape.circle), + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Decoration cellDecoration; + + /// The decoration for the today cell of [SfHijriDateRangePicker] year and + /// decade view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.decade, + /// selectionMode: DateRangePickerSelectionMode.range, + /// yearCellStyle: HijriDatePickerYearCellStyle( + /// todayCellDecoration: BoxDecoration( + /// color: Colors.red, + /// border: Border.all(color: const Color(0xFF2B732F), + /// width: 1), + /// shape: BoxShape.circle), + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Decoration todayCellDecoration; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + final HijriDatePickerYearCellStyle otherStyle = other; + return otherStyle.textStyle == textStyle && + otherStyle.todayTextStyle == todayTextStyle && + otherStyle.disabledDatesDecoration == disabledDatesDecoration && + otherStyle.cellDecoration == cellDecoration && + otherStyle.todayCellDecoration == todayCellDecoration && + otherStyle.disabledDatesTextStyle == disabledDatesTextStyle; + } + + @override + int get hashCode { + return hashValues(textStyle, todayTextStyle, disabledDatesTextStyle, + disabledDatesDecoration, cellDecoration, todayCellDecoration); + } +} + +/// Options to customize the month cells of the [SfHijriDateRangePicker]. +/// +/// +/// Allows to customize the [textStyle], [todayTextStyle], +/// [disabledDatesTextStyle], [blackoutDateTextStyle], [weekendTextStyle], +/// [specialDatesTextStyle], [specialDatesDecoration], +/// [blackoutDatesDecoration], [cellDecoration], [todayCellDecoration], +/// [disabledDatesDecoration],and [weekendDatesDecoration] in the month cells +/// of the date range picker. +/// +/// ``` dart +/// +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// home: Scaffold( +/// body: SfHijriDateRangePicker( +/// view: HijriDatePickerView.month, +/// enablePastDates: false, +/// monthCellStyle: HijriDatePickerMonthCellStyle( +/// textStyle: TextStyle( +/// fontWeight: FontWeight.w400, fontSize: 15, +/// color: Colors.black), +/// todayTextStyle: TextStyle( +/// fontStyle: FontStyle.italic, +/// fontSize: 15, +/// fontWeight: FontWeight.w500, +/// color: Colors.red), +/// disabledDatesDecoration: BoxDecoration( +/// color: const Color(0xFFDFDFDF).withOpacity(0.2), +/// border: Border.all(color: const Color(0xFFB6B6B6), width: 1), +/// shape: BoxShape.circle), +/// ), +/// ), +/// ), +/// ); +/// } +/// +/// ``` +@immutable +class HijriDatePickerMonthCellStyle { + /// Creates a date range picker month cell style for date range picker. + /// + /// 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}); + + /// The text style for the text in the [SfHijriDateRangePicker] month cells. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// monthCellStyle: HijriDatePickerMonthCellStyle( + /// textStyle: TextStyle( + /// fontStyle: FontStyle.normal, + /// fontWeight: FontWeight.w400, + /// fontSize: 12, + /// color: Colors.blue + /// ) + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final TextStyle textStyle; + + /// The text style for the text in the today cell of [SfHijriDateRangePicker] + /// month view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// monthCellStyle: HijriDatePickerMonthCellStyle( + /// todayTextStyle: TextStyle( + /// fontStyle: FontStyle.italic, + /// fontWeight: FontWeight.w400, + /// fontSize: 12, + /// color: Colors.red + /// ) + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final TextStyle todayTextStyle; + + /// The text style for the text in the disabled dates cell of + /// [SfHijriDateRangePicker] month view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// See also: + /// [SfHijriDateRangePicker.minDate]. + /// [SfHijriDateRangePicker.maxDate]. + /// [SfHijriDateRangePicker.enablePastDates]. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// selectionMode: DateRangePickerSelectionMode.range, + /// monthCellStyle: HijriDatePickerMonthCellStyle( + /// disabledDatesTextStyle: TextStyle( + /// fontStyle: FontStyle.normal, + /// fontWeight: FontWeight.w300, + /// fontSize: 10, + /// color: Colors.grey)), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final TextStyle disabledDatesTextStyle; + + /// The text style for the text in the blackout dates cell of + /// [SfHijriDateRangePicker] month view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// See also: [HijriDatePickerMonthViewSettings.blackoutDates]. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// monthCellStyle: HijriDatePickerMonthCellStyle( + /// blackoutDateTextStyle: TextStyle( + /// fontStyle: FontStyle.italic, + /// fontWeight: FontWeight.w500, + /// fontSize: 18, + /// color: Colors.black54)), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final TextStyle blackoutDateTextStyle; + + /// The text style for the text in the weekend dates cell of + /// [SfHijriDateRangePicker] month view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// See also: [HijriDatePickerMonthViewSettings.weekendDays]. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// monthCellStyle: HijriDatePickerMonthCellStyle( + /// weekendTextStyle: TextStyle( + /// fontStyle: FontStyle.italic, + /// fontWeight: FontWeight.w500, + /// fontSize: 12, + /// color: Colors.green)), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final TextStyle weekendTextStyle; + + /// The text style for the text in the special dates cell of + /// [SfHijriDateRangePicker] month view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// See also: [HijriDatePickerMonthViewSettings.specialDates]. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// monthCellStyle: HijriDatePickerMonthCellStyle( + /// specialDatesTextStyle: TextStyle( + /// fontWeight: FontWeight.bold, + /// fontSize: 12, + /// color: Colors.orange)), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final TextStyle specialDatesTextStyle; + + /// The decoration for the special date cells of [SfHijriDateRangePicker] + /// month view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// monthCellStyle: HijriDatePickerMonthCellStyle( + /// specialDatesDecoration: BoxDecoration( + /// color: Colors.blueGrey, + /// border: Border.all(color: const Color(0xFF2B732F), + /// width: 1), + /// shape: BoxShape.circle), + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Decoration specialDatesDecoration; + + /// The decoration for the weekend date cells of [SfHijriDateRangePicker] + /// month view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// monthCellStyle: HijriDatePickerMonthCellStyle( + /// weekendDatesDecoration: BoxDecoration( + /// color: Colors.green, + /// border: Border.all(color: const Color(0xFF2B732F), + /// width: 1), + /// shape: BoxShape.circle), + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Decoration weekendDatesDecoration; + + /// The decoration for the blackout date cells of [SfHijriDateRangePicker] + /// month view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// monthCellStyle: HijriDatePickerMonthCellStyle( + /// blackoutDatesDecoration: BoxDecoration( + /// color: Colors.black, + /// border: Border.all(color: const Color(0xFF2B732F), + /// width: 1), + /// shape: BoxShape.circle), + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Decoration blackoutDatesDecoration; + + /// The decoration for the disabled date cells of [SfHijriDateRangePicker] + /// month view. + /// + /// The disabled dates are the one which falls beyond the minimum and maximum + /// date range. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// See also: + /// [SfHijriDateRangePicker.minDate]. + /// [SfHijriDateRangePicker.maxDate]. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// monthCellStyle: HijriDatePickerMonthCellStyle( + /// disabledDatesDecoration: BoxDecoration( + /// color: Colors.black.withOpacity(0.4), + /// border: Border.all(color: const Color(0xFF2B732F), + /// width: 1), + /// shape: BoxShape.rectangle), + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Decoration disabledDatesDecoration; + + /// The decoration for the month cells of [SfHijriDateRangePicker] month view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// monthCellStyle: HijriDatePickerMonthCellStyle( + /// cellDecoration: BoxDecoration( + /// color: Colors.blueGrey.withOpacity(0.4), + /// border: Border.all(color: const Color(0xFF2B732F), + /// width: 1), + /// shape: BoxShape.circle), + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Decoration cellDecoration; + + /// The decoration for the today text cell of [SfHijriDateRangePicker] month + /// view. + /// + /// Defaults to null. + /// + /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the + /// appearance of various components of the date range picker. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// enablePastDates: false, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// monthCellStyle: HijriDatePickerMonthCellStyle( + /// todayCellDecoration: BoxDecoration( + /// color: Colors.red, + /// border: Border.all(color: const Color(0xFF2B732F), + /// width: 1), + /// shape: BoxShape.circle), + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final Decoration todayCellDecoration; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + final HijriDatePickerMonthCellStyle otherStyle = other; + return otherStyle.textStyle == textStyle && + otherStyle.todayTextStyle == todayTextStyle && + otherStyle.blackoutDateTextStyle == blackoutDateTextStyle && + otherStyle.weekendTextStyle == weekendTextStyle && + otherStyle.specialDatesTextStyle == specialDatesTextStyle && + otherStyle.specialDatesDecoration == specialDatesDecoration && + otherStyle.weekendDatesDecoration == weekendDatesDecoration && + otherStyle.blackoutDatesDecoration == blackoutDatesDecoration && + otherStyle.disabledDatesDecoration == disabledDatesDecoration && + otherStyle.cellDecoration == cellDecoration && + otherStyle.todayCellDecoration == todayCellDecoration && + otherStyle.disabledDatesTextStyle == disabledDatesTextStyle; + } + + @override + int get hashCode { + return hashList([ + textStyle, + todayTextStyle, + disabledDatesTextStyle, + specialDatesDecoration, + weekendDatesDecoration, + blackoutDatesDecoration, + disabledDatesDecoration, + cellDecoration, + todayCellDecoration, + specialDatesTextStyle, + blackoutDateTextStyle, + weekendTextStyle, + ]); + } +} + +/// An object that used for programmatic date navigation, date and range +/// selection and view switching in [SfHijriDateRangePicker]. +/// +/// A [HijriDatePickerController] served for several purposes. It can be +/// used to selected dates and ranges programmatically on +/// [SfHijriDateRangePicker] by using the [controller.selectedDate], +/// [controller.selectedDates], [controller.selectedRange], +/// [controller.selectedRanges]. It can be used to change the +/// [SfHijriDateRangePicker] view by using the [controller.view] property. +/// It can be used to navigate to specific date by using the +/// [controller.displayDate] property. +/// +/// ## Listening to property changes: +/// The [HijriDatePickerController] is a listenable. It notifies it's +/// listeners whenever any of attached [SfHijriDateRangePicker]`s selected date, +/// display date and view changed (i.e: selecting a different date, swiping to +/// next/previous view and navigates to different view] in in +/// [SfHijriDateRangePicker]. +/// +/// ## Navigates to different view: +/// The [SfHijriDateRangePicker] visible view can be changed by using the +/// [Controller.view] property, the property allow to change the view of +/// [SfHijriDateRangePicker] programmatically on initial load and in rum time. +/// +/// ## Programmatic selection: +/// In [SfHijriDateRangePicker] selecting dates programmatically can be achieved +/// by using the [controller.selectedDate], [controller.selectedDates], +/// [controller.selectedRange], [controller.selectedRanges] which allows to +/// select the dates or ranges programmatically on [SfHijriDateRangePicker] on +/// initial load and in run time. +/// +/// See also: [HijriDateRangePickerSelectionMode] +/// +/// Defaults to null. +/// +/// This example demonstrates how to use the [HijriDatePickerController] for +/// [SfHijriDateRangePicker]. +/// +/// ``` dart +/// +///class MyApp extends StatefulWidget { +/// @override +/// MyAppState createState() => MyAppState(); +///} +/// +///class MyAppState extends State { +/// HijriDatePickerController _pickerController; +/// +/// @override +/// void initState() { +/// _pickerController = HijriDatePickerController(); +/// _pickerController.selectedDates = [ +/// HijriDateTime.now().add(Duration(days: 2)), +/// HijriDateTime.now().add(Duration(days: 4)), +/// HijriDateTime.now().add(Duration(days: 7)), +/// HijriDateTime.now().add(Duration(days: 11)) +/// ]; +/// _pickerController.displayDate = HijriDateTime.now(); +/// _pickerController.addPropertyChangedListener(handlePropertyChange); +/// super.initState(); +/// } +/// +/// void handlePropertyChange(String propertyName) { +/// if (propertyName == 'selectedDates') { +/// final List selectedDates = +/// _pickerController.selectedDates; +/// } else if (propertyName == 'displayDate') { +/// final HijriDateTime displayDate = _pickerController.displayDate; +/// } +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// home: Scaffold( +/// body: SfHijriDateRangePicker( +/// view: HijriDatePickerView.month, +/// controller: _pickerController, +/// selectionMode: HijriDateRangePickerSelectionMode.multiple, +/// ), +/// ), +/// ); +/// } +///} +/// +/// ``` +class HijriDatePickerController extends DateRangePickerValueChangeNotifier { + HijriDateTime _selectedDate; + List _selectedDates; + HijriDateRange _selectedRange; + List _selectedRanges; + HijriDateTime _displayDate; + HijriDatePickerView _view; + + /// The selected date in the [SfHijriDateRangePicker]. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.single] for other selection modes this + /// property will return as null. + HijriDateTime get selectedDate => _selectedDate; + + /// Selects the given date programmatically in the [SfHijriDateRangePicker] by + /// checking that the date falls in between the minimum and maximum date + /// range. + /// + /// _Note:_ If any date selected previously, will be removed and the selection + /// will be drawn to the date given in this property. + /// + /// If it is not [null] the widget will render the date selection for the date + /// set to this property, even the + /// [SfHijriDateRangePicker.initialSelectedDate] is not null. + /// + /// It is only applicable when the [DateRangePickerSelectionMode] set as + /// [DateRangePickerSelectionMode.single]. + /// + /// ``` dart + /// + /// class MyAppState extends State { + /// DateRangePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = HijriDatePickerController(); + /// _pickerController.selectedDate = HijriDateTime.now().add((Duration( + /// days: 4))); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: HijriDateRangePickerSelectionMode.single, + /// showNavigationArrow: true, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + set selectedDate(HijriDateTime date) { + if (isSameDate(_selectedDate, date)) { + return; + } + + _selectedDate = date; + notifyPropertyChangedListeners('selectedDate'); + } + + /// The list of dates selected in the [SfHijriDateRangePicker]. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.multiple] for other selection modes + /// this property will return as null. + List get selectedDates => _selectedDates; + + /// Selects the given dates programmatically in the [SfHijriDateRangePicker] + /// by checking that the dates falls in between the minimum and maximum date + /// range. + /// + /// _Note:_ If any list of dates selected previously, will be removed and the + /// selection will be drawn to the dates set to this property. + /// + /// If it is not [null] the widget will render the date selection for the + /// dates set to this property, even the + /// [SfHijriDateRangePicker.initialSelectedDates] is not null. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.multiple]. + /// + /// ``` dart + /// + /// class MyAppState extends State { + /// DateRangePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = HijriDatePickerController(); + /// _pickerController.selectedDates = [ + /// HijriDateTime.now().add((Duration(days: 4))), + /// HijriDateTime.now().add((Duration(days: 7))), + /// HijriDateTime.now().add((Duration(days: 8))) + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.multiple, + /// showNavigationArrow: true, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + set selectedDates(List dates) { + if (DateRangePickerHelper.isDateCollectionEquals(_selectedDates, dates)) { + return; + } + + _selectedDates = DateRangePickerHelper.cloneList(dates); + notifyPropertyChangedListeners('selectedDates'); + } + + /// selected date range in the [SfHijriDateRangePicker]. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.range] for other selection modes this + /// property will return as null. + HijriDateRange get selectedRange => _selectedRange; + + /// Selects the given date range programmatically in the + /// [SfHijriDateRangePicker] by checking that the range of dates falls in + /// between the minimum and maximum date range. + /// + /// _Note:_ If any date range selected previously, will be removed and the + /// selection will be drawn to the range of dates set to this property. + /// + /// If it is not [null] the widget will render the date selection for the + /// range set to this property, even the + /// [SfHijriDateRangePicker.initialSelectedRange] is not null. + /// + /// It is only applicable when the [selectionMode] set as + /// [HijriDateRangePickerSelectionMode.range]. + /// + /// ``` dart + /// + /// class MyAppState extends State { + /// DateRangePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = HijriDatePickerController(); + /// _pickerController.selectedRange = HijriPickerDateRange( + /// HijriDateTime.now().add(Duration(days: 4)), + /// HijriDateTime.now().add(Duration(days: 5))); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.range, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + set selectedRange(HijriDateRange range) { + if (DateRangePickerHelper.isRangeEquals(_selectedRange, range)) { + return; + } + + _selectedRange = range; + notifyPropertyChangedListeners('selectedRange'); + } + + /// List of selected ranges in the [SfHijriDateRangePicker]. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.multiRange] for other selection modes + /// this property will return as null. + List get selectedRanges => _selectedRanges; + + /// Selects the given date ranges programmatically in the + /// [SfHijriDateRangePicker] by checking that the ranges of dates falls in + /// between the minimum and maximum date range. + /// + /// If it is not [null] the widget will render the date selection for the + /// ranges set to this property, even the + /// [SfHijriDateRangePicker.initialSelectedRanges] is not null. + /// + /// _Note:_ If any date ranges selected previously, will be removed and the + /// selection will be drawn to the ranges of dates set to this property. + /// + /// It is only applicable when the [selectionMode] set as + /// [DateRangePickerSelectionMode.multiRange]. + /// + /// ``` dart + /// + /// class MyAppState extends State { + /// HijriDatePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = HijriDatePickerController(); + /// _pickerController.selectedRanges = [ + /// PickerDateRange(HijriDateTime.now().subtract(Duration(days: 4)), + /// HijriDateTime.now().add(Duration(days: 4))), + /// PickerDateRange(HijriDateTime.now().add(Duration(days: 11)), + /// HijriDateTime.now().add(Duration(days: 16))) + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.multiRange, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + set selectedRanges(List ranges) { + if (DateRangePickerHelper.isDateRangesEquals(_selectedRanges, ranges)) { + return; + } + + _selectedRanges = DateRangePickerHelper.cloneList(ranges); + notifyPropertyChangedListeners('selectedRanges'); + } + + /// The first date of the current visible view month, when the + /// [HijriDatePickerMonthViewSettings.numberOfWeeksInView] set with + /// default value 6. + /// + /// If the [HijriDatePickerMonthViewSettings.numberOfWeeksInView] + /// property set with value other then 6, this will return the first visible + /// date of the current month. + HijriDateTime get displayDate => _displayDate; + + /// Navigates to the given date programmatically without any animation in the + /// [SfHijriDateRangePicker] by checking that the date falls in between the + /// [SfHijriDateRangePicker.minDate] and [SfHijriDateRangePicker.maxDate] + /// date range. + /// + /// If the date falls beyond the [SfHijriDateRangePicker.minDate] and + /// [SfHijriDateRangePicker.maxDate] the widget will move the widgets min or + /// max date. + /// + /// + /// ``` dart + /// + /// class MyAppState extends State { + /// HijriDatePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = HijriDatePickerController(); + /// _pickerController.displayDate = HijriDateTime(2022, 02, 05); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.single, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + set displayDate(HijriDateTime date) { + if (isSameDate(_displayDate, date)) { + return; + } + + _displayDate = date; + notifyPropertyChangedListeners('displayDate'); + } + + /// The current visible [HijriDatePickerView] of + /// [SfHijriDateRangePicker]. + HijriDatePickerView get view => _view; + + /// Set the [HijriDatePickerView] for the [SfHijriDateRangePicker]. + /// + /// + /// The [SfHijriDateRangePicker] will display the view sets to this property. + /// + /// ```dart + /// + /// class MyAppState extends State { + /// HijriDatePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = HijriDatePickerController(); + /// _pickerController.view = HijriDatePickerView.year; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.single, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + set view(HijriDatePickerView value) { + if (_view == value) { + return; + } + + _view = value; + notifyPropertyChangedListeners('view'); + } + + /// Moves to the next view programmatically with animation by checking that + /// the next view dates falls between the minimum and maximum date range. + /// + /// _Note:_ If the current view has the maximum date range, it will not move + /// to the next view. + /// + /// ```dart + /// + /// class MyApp extends StatefulWidget { + /// @override + /// MyAppState createState() => MyAppState(); + ///} + /// + ///class MyAppState extends State { + /// HijriDatePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = HijriDatePickerController(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// actions: [ + /// IconButton( + /// icon: Icon(Icons.arrow_forward), + /// onPressed: () { + /// _pickerController.forward(); + /// }, + /// ) + /// ], + /// title: Text('Date Range Picker Demo'), + /// leading: IconButton( + /// icon: Icon(Icons.arrow_back), + /// onPressed: () { + /// _pickerController.backward(); + /// }, + /// ), + /// ), + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.single, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + VoidCallback forward; + + /// Moves to the previous view programmatically with animation by checking + /// that the previous view dates falls between the minimum and maximum date + /// range. + /// + /// _Note:_ If the current view has the minimum date range, it will not move + /// to the previous view. + /// + /// ```dart + /// + /// class MyApp extends StatefulWidget { + /// @override + /// MyAppState createState() => MyAppState(); + ///} + /// + ///class MyAppState extends State { + /// HijriDatePickerController _pickerController; + /// + /// @override + /// void initState() { + /// _pickerController = HijriDatePickerController(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// actions: [ + /// IconButton( + /// icon: Icon(Icons.arrow_forward), + /// onPressed: () { + /// _pickerController.forward(); + /// }, + /// ) + /// ], + /// title: Text('Date Range Picker Demo'), + /// leading: IconButton( + /// icon: Icon(Icons.arrow_back), + /// onPressed: () { + /// _pickerController.backward(); + /// }, + /// ), + /// ), + /// body: SfHijriDateRangePicker( + /// controller: _pickerController, + /// view: HijriDatePickerView.month, + /// selectionMode: DateRangePickerSelectionMode.single, + /// ), + /// ), + /// ); + /// } + ///} + /// + /// ``` + VoidCallback backward; +} + +/// Available views for [SfHijriDateRangePicker]. +enum HijriDatePickerView { + /// - HijriDatePickerView.month, Displays the month view. + month, + + /// - HijriDatePickerView.year, Displays the year view. + year, + + /// - HijriDatePickerView.decade, Displays the decade view. + decade, +} + +/// The dates that visible on the view changes in [SfHIjriDateRangePicker]. +/// +/// Details for [HijriDatePickerViewChangedCallback], such as +/// [visibleDateRange] and [view]. +@immutable +class HijriDatePickerViewChangedArgs { + /// Creates details for [DateRangePickerViewChangedCallback]. + const HijriDatePickerViewChangedArgs(this.visibleDateRange, this.view); + + /// The date range of the currently visible view dates. + /// + /// See also: [HijriDateRange]. + final HijriDateRange visibleDateRange; + + /// The currently visible [HijriDatePickerView] in the + /// [SfHijriDateRangePicker]. + /// + /// See also: [HijriDatePickerView]. + final HijriDatePickerView view; +} + +/// Defines a range of dates, covers the dates in between the given [startDate] +/// and [endDate] as a range. +@immutable +class HijriDateRange { + /// Creates a picker date range with the given start and end date. + const HijriDateRange(this.startDate, this.endDate); + + /// The start date of the range. + final HijriDateTime startDate; + + /// The end date of the range. + final HijriDateTime endDate; +} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/looping_widget/picker_scroll_view.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/looping_widget/picker_scroll_view.dart deleted file mode 100644 index 5c8e43e94..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/looping_widget/picker_scroll_view.dart +++ /dev/null @@ -1,1838 +0,0 @@ -part of datepicker; - -@immutable -class _PickerScrollView extends StatefulWidget { - const _PickerScrollView(this.picker, this.controller, this.width, this.height, - this.isRtl, this.datePickerTheme, this.locale, this.textScaleFactor, - {Key key, this.getPickerStateValues, this.updatePickerStateValues}) - : super(key: key); - - final SfDateRangePicker picker; - final double width; - final double height; - final bool isRtl; - final _UpdatePickerState getPickerStateValues; - final _UpdatePickerState updatePickerStateValues; - final DateRangePickerController controller; - final SfDateRangePickerThemeData datePickerTheme; - final Locale locale; - final double textScaleFactor; - - @override - _PickerScrollViewState createState() => _PickerScrollViewState(); -} - -class _PickerScrollViewState extends State<_PickerScrollView> - with TickerProviderStateMixin { - // three views to arrange the view in vertical/horizontal direction and handle the swiping - _PickerView _currentView, _nextView, _previousView; - - // the three children which to be added into the layout - List<_PickerView> _children; - - // holds the index of the current displaying view - int _currentChildIndex; - - // _scrollStartPosition contains the touch movement starting position - // _position contains distance that the view swiped - double _scrollStartPosition, _position; - - // animation controller to control the animation - AnimationController _animationController; - - // animation handled for the view swiping - Animation _animation; - - // tween animation to handle the animation - Tween _tween; - - // three visible dates for the three views, the dates will updated based on - // the swiping in the swipe end _currentViewVisibleDates which stores the - // visible dates of the current displaying view - List _visibleDates, - _previousViewVisibleDates, - _nextViewVisibleDates, - _currentViewVisibleDates; - - /// keys maintained to access the data and methods from the calendar view - /// class. - GlobalKey<_PickerViewState> _previousViewKey, _currentViewKey, _nextViewKey; - - _PickerStateArgs _pickerStateDetails; - FocusNode _focusNode; - - @override - void initState() { - _previousViewKey = GlobalKey<_PickerViewState>(); - _currentViewKey = GlobalKey<_PickerViewState>(); - _nextViewKey = GlobalKey<_PickerViewState>(); - _focusNode = FocusNode(); - _pickerStateDetails = _PickerStateArgs(); - _currentChildIndex = 1; - _updateVisibleDates(); - _animationController = AnimationController( - duration: const Duration(milliseconds: 250), - vsync: this, - animationBehavior: AnimationBehavior.normal); - _tween = Tween(begin: 0.0, end: 0.1); - _animation = _tween.animate(_animationController) - ..addListener(animationListener); - - super.initState(); - } - - @override - void didUpdateWidget(_PickerScrollView oldWidget) { - if (widget.picker.navigationDirection != - oldWidget.picker.navigationDirection || - widget.width != oldWidget.width || - oldWidget.datePickerTheme != widget.datePickerTheme || - widget.picker.viewSpacing != oldWidget.picker.viewSpacing || - widget.picker.selectionMode != oldWidget.picker.selectionMode || - widget.height != oldWidget.height) { - _position = 0; - _children.clear(); - } - - if (oldWidget.textScaleFactor != widget.textScaleFactor) { - _position = 0; - _children.clear(); - } - - if (oldWidget.picker.controller != widget.picker.controller) { - _position = 0; - _updateSelectedValuesWithMinMaxDate(); - _children.clear(); - _updateVisibleDates(); - } - - if (widget.isRtl != oldWidget.isRtl || - widget.picker.enableMultiView != oldWidget.picker.enableMultiView) { - _position = 0; - _children.clear(); - _updateVisibleDates(); - } - - _updateSettings(oldWidget); - - if (widget.controller.view == DateRangePickerView.year && - (widget.picker.monthFormat != oldWidget.picker.monthFormat || - widget.picker.yearCellStyle != oldWidget.picker.yearCellStyle)) { - _position = 0; - _children.clear(); - } - - if (widget.picker.minDate != oldWidget.picker.minDate || - widget.picker.maxDate != oldWidget.picker.maxDate) { - final DateTime previousVisibleDate = _pickerStateDetails._currentDate; - widget.getPickerStateValues(_pickerStateDetails); - if (!isSameDate(_pickerStateDetails._currentDate, previousVisibleDate)) { - _updateVisibleDates(); - } - - _position = 0; - _updateSelectedValuesWithMinMaxDate(); - _children.clear(); - } - - if (widget.picker.enablePastDates != oldWidget.picker.enablePastDates) { - _position = 0; - _updateSelectedValuesWithMinMaxDate(); - _children.clear(); - } - - if (widget.controller.view == DateRangePickerView.month && - (oldWidget.picker.monthViewSettings.viewHeaderStyle != - widget.picker.monthViewSettings.viewHeaderStyle || - oldWidget.picker.monthViewSettings.viewHeaderHeight != - widget.picker.monthViewSettings.viewHeaderHeight || - widget.picker.monthViewSettings.showTrailingAndLeadingDates != - oldWidget - .picker.monthViewSettings.showTrailingAndLeadingDates)) { - _children.clear(); - _position = 0; - } - - if (widget.picker.monthViewSettings.numberOfWeeksInView != - oldWidget.picker.monthViewSettings.numberOfWeeksInView || - widget.picker.monthViewSettings.firstDayOfWeek != - oldWidget.picker.monthViewSettings.firstDayOfWeek) { - _updateVisibleDates(); - _position = 0; - } - - /// Update the selection when [allowViewNavigation] property in - /// [SfDateRangePicker] changed with current picker view not as month view. - /// because year, decade and century views highlight selection when - /// [allowViewNavigation] property value as false. - if (oldWidget.picker.allowViewNavigation != - widget.picker.allowViewNavigation && - widget.controller.view != DateRangePickerView.month) { - _drawYearSelection(); - } - - if (oldWidget.picker.controller != widget.picker.controller || - widget.picker.controller == null) { - widget.getPickerStateValues(_pickerStateDetails); - super.didUpdateWidget(oldWidget); - return; - } - - if (oldWidget.picker.controller.displayDate != - widget.picker.controller.displayDate || - !isSameDate( - _pickerStateDetails._currentDate, widget.controller.displayDate)) { - _pickerStateDetails._currentDate = widget.picker.controller.displayDate; - _updateVisibleDates(); - } - - _drawSelection(oldWidget); - - if (widget.picker.controller.view != oldWidget.picker.controller.view || - _pickerStateDetails._view != widget.controller._view) { - _pickerStateDetails._view = widget.controller._view; - _position = 0; - _children.clear(); - _updateVisibleDates(); - } - - widget.getPickerStateValues(_pickerStateDetails); - super.didUpdateWidget(oldWidget); - } - - @override - Widget build(BuildContext context) { - double leftPosition, rightPosition, topPosition, bottomPosition; - switch (widget.picker.navigationDirection) { - case DateRangePickerNavigationDirection.horizontal: - { - leftPosition = leftPosition ?? -widget.width; - rightPosition = rightPosition ?? -widget.width; - topPosition = 0; - bottomPosition = 0; - } - break; - case DateRangePickerNavigationDirection.vertical: - { - leftPosition = 0; - rightPosition = 0; - topPosition = topPosition ?? -widget.height; - bottomPosition = bottomPosition ?? -widget.height; - } - } - - return Stack( - children: [ - Positioned( - left: leftPosition, - right: rightPosition, - bottom: bottomPosition, - top: topPosition, - child: GestureDetector( - child: RawKeyboardListener( - focusNode: _focusNode, - onKey: _onKeyDown, - child: CustomScrollViewerLayout( - _addViews(context), - widget.picker.navigationDirection == - DateRangePickerNavigationDirection.horizontal - ? CustomScrollDirection.horizontal - : CustomScrollDirection.vertical, - _position, - _currentChildIndex), - ), - onHorizontalDragStart: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.horizontal - ? _onHorizontalStart - : null, - onHorizontalDragUpdate: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.horizontal - ? _onHorizontalUpdate - : null, - onHorizontalDragEnd: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.horizontal - ? _onHorizontalEnd - : null, - onVerticalDragStart: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical - ? _onVerticalStart - : null, - onVerticalDragUpdate: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical - ? _onVerticalUpdate - : null, - onVerticalDragEnd: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical - ? _onVerticalEnd - : null, - ), - ) - ], - ); - } - - @override - void dispose() { - _animationController?.dispose(); - if (_animation != null) { - _animation.removeListener(animationListener); - } - super.dispose(); - } - - void _updateVisibleDates() { - widget.getPickerStateValues(_pickerStateDetails); - final DateTime currentDate = _pickerStateDetails._currentDate; - final DateTime prevDate = _getPreviousViewStartDate( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - _pickerStateDetails._currentDate, - widget.isRtl); - final DateTime nextDate = _getNextViewStartDate( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - _pickerStateDetails._currentDate, - widget.isRtl); - - DateTime afterNextViewDate; - List afterVisibleDates; - if (widget.picker.enableMultiView) { - afterNextViewDate = _getNextViewStartDate( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.isRtl ? prevDate : nextDate, - false); - } - - switch (widget.controller.view) { - case DateRangePickerView.month: - { - _visibleDates = getVisibleDates( - currentDate, - null, - widget.picker.monthViewSettings.firstDayOfWeek, - _getViewDatesCount(widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView)); - _previousViewVisibleDates = getVisibleDates( - prevDate, - null, - widget.picker.monthViewSettings.firstDayOfWeek, - _getViewDatesCount(widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView)); - _nextViewVisibleDates = getVisibleDates( - nextDate, - null, - widget.picker.monthViewSettings.firstDayOfWeek, - _getViewDatesCount(widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView)); - if (widget.picker.enableMultiView) { - afterVisibleDates = getVisibleDates( - afterNextViewDate, - null, - widget.picker.monthViewSettings.firstDayOfWeek, - _getViewDatesCount(widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView)); - } - } - break; - case DateRangePickerView.decade: - case DateRangePickerView.year: - case DateRangePickerView.century: - { - _visibleDates = - _getVisibleYearDates(currentDate, widget.controller.view); - _previousViewVisibleDates = - _getVisibleYearDates(prevDate, widget.controller.view); - _nextViewVisibleDates = - _getVisibleYearDates(nextDate, widget.controller.view); - if (widget.picker.enableMultiView) { - afterVisibleDates = - _getVisibleYearDates(afterNextViewDate, widget.controller.view); - } - } - } - - if (widget.picker.enableMultiView) { - _updateVisibleDatesForMultiView(afterVisibleDates); - } - - _currentViewVisibleDates = _visibleDates; - _pickerStateDetails._currentViewVisibleDates = _currentViewVisibleDates; - widget.updatePickerStateValues(_pickerStateDetails); - - if (_currentChildIndex == 0) { - _visibleDates = _nextViewVisibleDates; - _nextViewVisibleDates = _previousViewVisibleDates; - _previousViewVisibleDates = _currentViewVisibleDates; - } else if (_currentChildIndex == 1) { - _visibleDates = _currentViewVisibleDates; - } else if (_currentChildIndex == 2) { - _visibleDates = _previousViewVisibleDates; - _previousViewVisibleDates = _nextViewVisibleDates; - _nextViewVisibleDates = _currentViewVisibleDates; - } - } - - void _updateVisibleDatesForMultiView(List afterVisibleDates) { - if (widget.isRtl) { - for (int i = 0; i < _visibleDates.length; i++) { - _nextViewVisibleDates.add(_visibleDates[i]); - } - for (int i = 0; i < _previousViewVisibleDates.length; i++) { - _visibleDates.add(_previousViewVisibleDates[i]); - } - for (int i = 0; i < afterVisibleDates.length; i++) { - _previousViewVisibleDates.add(afterVisibleDates[i]); - } - } else { - for (int i = 0; i < _visibleDates.length; i++) { - _previousViewVisibleDates.add(_visibleDates[i]); - } - for (int i = 0; i < _nextViewVisibleDates.length; i++) { - _visibleDates.add(_nextViewVisibleDates[i]); - } - for (int i = 0; i < afterVisibleDates.length; i++) { - _nextViewVisibleDates.add(afterVisibleDates[i]); - } - } - } - - void _updateNextViewVisibleDates() { - DateTime currentViewDate = _currentViewVisibleDates[0]; - if ((widget.controller.view == DateRangePickerView.month && - widget.picker.monthViewSettings.numberOfWeeksInView == 6) || - widget.controller.view == DateRangePickerView.year || - widget.controller.view == DateRangePickerView.decade || - widget.controller.view == DateRangePickerView.century) { - currentViewDate = _currentViewVisibleDates[ - (_currentViewVisibleDates.length / - (widget.picker.enableMultiView ? 4 : 2)) - .truncate()]; - } - - currentViewDate = _getNextViewStartDate( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - currentViewDate, - widget.isRtl); - List afterVisibleDates; - DateTime afterNextViewDate; - if (widget.picker.enableMultiView && !widget.isRtl) { - afterNextViewDate = _getNextViewStartDate( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - currentViewDate, - widget.isRtl); - } - List dates; - switch (widget.controller.view) { - case DateRangePickerView.month: - { - dates = getVisibleDates( - currentViewDate, - null, - widget.picker.monthViewSettings.firstDayOfWeek, - _getViewDatesCount(widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView)); - if (widget.picker.enableMultiView && !widget.isRtl) { - afterVisibleDates = getVisibleDates( - afterNextViewDate, - null, - widget.picker.monthViewSettings.firstDayOfWeek, - _getViewDatesCount(widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView)); - } - } - break; - case DateRangePickerView.year: - case DateRangePickerView.decade: - case DateRangePickerView.century: - { - dates = _getVisibleYearDates(currentViewDate, widget.controller.view); - if (widget.picker.enableMultiView && !widget.isRtl) { - afterVisibleDates = - _getVisibleYearDates(afterNextViewDate, widget.controller.view); - } - } - } - - if (widget.picker.enableMultiView) { - dates.addAll(_updateNextVisibleDateForMultiView(afterVisibleDates)); - } - - if (_currentChildIndex == 0) { - _nextViewVisibleDates = dates; - } else if (_currentChildIndex == 1) { - _previousViewVisibleDates = dates; - } else { - _visibleDates = dates; - } - } - - List _updateNextVisibleDateForMultiView( - List afterVisibleDates) { - final List dates = []; - if (!widget.isRtl) { - for (int i = 0; i < afterVisibleDates.length; i++) { - dates.add(afterVisibleDates[i]); - } - } else { - for (int i = 0; i < _currentViewVisibleDates.length ~/ 2; i++) { - dates.add(_currentViewVisibleDates[i]); - } - } - - return dates; - } - - void _updatePreviousViewVisibleDates() { - DateTime currentViewDate = _currentViewVisibleDates[0]; - if ((widget.controller.view == DateRangePickerView.month && - widget.picker.monthViewSettings.numberOfWeeksInView == 6) || - widget.controller.view == DateRangePickerView.year || - widget.controller.view == DateRangePickerView.decade || - widget.controller.view == DateRangePickerView.century) { - currentViewDate = _currentViewVisibleDates[ - (_currentViewVisibleDates.length / - (widget.picker.enableMultiView ? 4 : 2)) - .truncate()]; - } - - currentViewDate = _getPreviousViewStartDate( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - currentViewDate, - widget.isRtl); - List dates; - List afterVisibleDates; - DateTime afterNextViewDate; - if (widget.picker.enableMultiView && widget.isRtl) { - afterNextViewDate = _getPreviousViewStartDate( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - currentViewDate, - widget.isRtl); - } - - switch (widget.controller.view) { - case DateRangePickerView.month: - { - dates = getVisibleDates( - currentViewDate, - null, - widget.picker.monthViewSettings.firstDayOfWeek, - _getViewDatesCount(widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView)); - if (widget.picker.enableMultiView && widget.isRtl) { - afterVisibleDates = getVisibleDates( - afterNextViewDate, - null, - widget.picker.monthViewSettings.firstDayOfWeek, - _getViewDatesCount(widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView)); - } - } - break; - case DateRangePickerView.year: - case DateRangePickerView.decade: - case DateRangePickerView.century: - { - dates = _getVisibleYearDates(currentViewDate, widget.controller.view); - if (widget.picker.enableMultiView && widget.isRtl) { - afterVisibleDates = - _getVisibleYearDates(afterNextViewDate, widget.controller.view); - } - } - } - - if (widget.picker.enableMultiView) { - dates.addAll(_updatePreviousDatesForMultiView(afterVisibleDates)); - } - - if (_currentChildIndex == 0) { - _visibleDates = dates; - } else if (_currentChildIndex == 1) { - _nextViewVisibleDates = dates; - } else { - _previousViewVisibleDates = dates; - } - } - - List _updatePreviousDatesForMultiView( - List afterVisibleDates) { - final List dates = []; - if (widget.isRtl) { - for (int i = 0; i < (afterVisibleDates.length); i++) { - dates.add(afterVisibleDates[i]); - } - } else { - for (int i = 0; i < (_currentViewVisibleDates.length / 2); i++) { - dates.add(_currentViewVisibleDates[i]); - } - } - return dates; - } - - void _getPickerViewStateDetails(_PickerStateArgs details) { - details._currentViewVisibleDates = _currentViewVisibleDates; - details._currentDate = _pickerStateDetails._currentDate; - details._selectedDate = _pickerStateDetails._selectedDate; - details._selectedDates = _pickerStateDetails._selectedDates; - details._selectedRange = _pickerStateDetails._selectedRange; - details._selectedRanges = _pickerStateDetails._selectedRanges; - details._view = _pickerStateDetails._view; - } - - void _updatePickerViewStateDetails(_PickerStateArgs details) { - _pickerStateDetails._currentDate = details._currentDate; - _pickerStateDetails._selectedDate = details._selectedDate; - _pickerStateDetails._selectedDates = details._selectedDates; - _pickerStateDetails._selectedRange = details._selectedRange; - _pickerStateDetails._selectedRanges = details._selectedRanges; - _pickerStateDetails._view = details._view; - widget.updatePickerStateValues(_pickerStateDetails); - } - - _PickerView _getView(List dates, GlobalKey key) { - return _PickerView( - widget.picker, - widget.controller, - dates, - widget.width, - widget.height, - widget.datePickerTheme, - _focusNode, - widget.textScaleFactor, - key: key, - getPickerStateDetails: (_PickerStateArgs details) { - _getPickerViewStateDetails(details); - }, - updatePickerStateDetails: (_PickerStateArgs details) { - _updatePickerViewStateDetails(details); - }, - isRtl: widget.isRtl, - ); - } - - List _addViews(BuildContext context) { - _children = _children ?? <_PickerView>[]; - if (_children != null && _children.isEmpty) { - _previousView = _getView(_previousViewVisibleDates, _previousViewKey); - _currentView = _getView(_visibleDates, _currentViewKey); - _nextView = _getView(_nextViewVisibleDates, _nextViewKey); - - _children.add(_previousView); - _children.add(_currentView); - _children.add(_nextView); - return _children; - } - - final _PickerView previousView = _updateViews( - _previousView, _previousView.visibleDates, _previousViewVisibleDates); - final _PickerView currentView = - _updateViews(_currentView, _currentView.visibleDates, _visibleDates); - final _PickerView nextView = - _updateViews(_nextView, _nextView.visibleDates, _nextViewVisibleDates); - - //// Update views while the all day view height differ from original height, - //// else repaint the appointment painter while current child visible appointment not equals calendar visible appointment - if (_previousView != previousView) { - _previousView = previousView; - } - if (_currentView != currentView) { - _currentView = currentView; - } - if (_nextView != nextView) { - _nextView = nextView; - } - - return _children; - } - - // method to check and update the views and appointments on the swiping end - Widget _updateViews( - Widget view, List viewDates, List visibleDates) { - final int index = _children.indexOf(view); - // update the view with the visible dates on swiping end. - if (viewDates != visibleDates) { - view = _getView(visibleDates, view.key); - _children[index] = view; - } // check and update the visible appointments in the view - - return view; - } - - void animationListener() { - setState(() { - _position = _animation.value; - }); - } - - void _updateSettings(_PickerScrollView oldWidget) { - //// condition to check and update the view when the settings changed, it will check each and every property of settings - //// to avoid unwanted repainting - if (oldWidget.picker.monthViewSettings != widget.picker.monthViewSettings || - oldWidget.picker.monthCellStyle != widget.picker.monthCellStyle || - oldWidget.picker.selectionRadius != widget.picker.selectionRadius || - oldWidget.picker.startRangeSelectionColor != - widget.picker.startRangeSelectionColor || - oldWidget.picker.endRangeSelectionColor != - widget.picker.endRangeSelectionColor || - oldWidget.picker.rangeSelectionColor != - widget.picker.rangeSelectionColor || - oldWidget.picker.selectionColor != widget.picker.selectionColor || - oldWidget.picker.selectionTextStyle != - widget.picker.selectionTextStyle || - oldWidget.picker.rangeTextStyle != widget.picker.rangeTextStyle || - oldWidget.picker.monthViewSettings.blackoutDates != - widget.picker.monthViewSettings.blackoutDates || - oldWidget.picker.monthViewSettings.specialDates != - widget.picker.monthViewSettings.specialDates || - oldWidget.picker.monthViewSettings.weekendDays != - widget.picker.monthViewSettings.weekendDays || - oldWidget.picker.selectionShape != widget.picker.selectionShape || - oldWidget.picker.todayHighlightColor != - widget.picker.todayHighlightColor || - oldWidget.locale != widget.locale) { - _children.clear(); - _position = 0; - } - } - - void _drawSelection(_PickerScrollView oldWidget) { - switch (widget.picker.selectionMode) { - case DateRangePickerSelectionMode.single: - { - if ((oldWidget.picker.controller.selectedDate != - widget.picker.controller.selectedDate || - !isSameDate(_pickerStateDetails._selectedDate, - widget.controller.selectedDate))) { - _pickerStateDetails._selectedDate = - widget.picker.controller.selectedDate; - if (widget.controller.view != DateRangePickerView.month && - !widget.picker.allowViewNavigation) { - _drawYearSelection(); - } else { - _drawMonthSelection(); - } - - _position = 0; - } - } - break; - case DateRangePickerSelectionMode.multiple: - { - if (oldWidget.picker.controller.selectedDates != - widget.picker.controller.selectedDates || - !_isDateCollectionEquals(_pickerStateDetails._selectedDates, - widget.picker.controller.selectedDates)) { - _pickerStateDetails._selectedDates = - widget.picker.controller.selectedDates; - if (widget.controller.view != DateRangePickerView.month && - !widget.picker.allowViewNavigation) { - _drawYearSelection(); - } else { - _drawMonthSelection(); - } - - _position = 0; - } - } - break; - case DateRangePickerSelectionMode.range: - { - if (oldWidget.picker.controller.selectedRange != - widget.picker.controller.selectedRange || - !_isRangeEquals(_pickerStateDetails._selectedRange, - widget.picker.controller.selectedRange)) { - _pickerStateDetails._selectedRange = - widget.picker.controller.selectedRange; - if (widget.controller.view != DateRangePickerView.month && - !widget.picker.allowViewNavigation) { - _drawYearSelection(); - } else { - _drawMonthSelection(); - } - - _position = 0; - } - } - break; - case DateRangePickerSelectionMode.multiRange: - { - if (oldWidget.picker.controller.selectedRanges != - widget.picker.controller.selectedRanges || - !_isDateRangesEquals(_pickerStateDetails._selectedRanges, - widget.picker.controller.selectedRanges)) { - _pickerStateDetails._selectedRanges = - widget.picker.controller.selectedRanges; - if (widget.controller.view != DateRangePickerView.month && - !widget.picker.allowViewNavigation) { - _drawYearSelection(); - } else { - _drawMonthSelection(); - } - - _position = 0; - } - } - } - } - - bool _isDisabledDate(DateTime selectedDate) { - return !_isEnabledDate(widget.picker.minDate, widget.picker.maxDate, - widget.picker.enablePastDates, selectedDate); - } - - bool _isDisabledRange(PickerDateRange range) { - if ((_pickerStateDetails._selectedRange.startDate != null && - !_isEnabledDate( - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - _pickerStateDetails._selectedRange.startDate)) || - (_pickerStateDetails._selectedRange.endDate != null && - !_isEnabledDate( - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - _pickerStateDetails._selectedRange.endDate))) { - return true; - } - return false; - } - - void _updateSelectedValuesWithMinMaxDate() { - widget.getPickerStateValues(_pickerStateDetails); - switch (widget.picker.selectionMode) { - case DateRangePickerSelectionMode.single: - { - if (_pickerStateDetails._selectedDate != null) { - final bool isDisabled = - _isDisabledDate(_pickerStateDetails._selectedDate); - if (isDisabled) { - _pickerStateDetails._selectedDate = null; - widget.updatePickerStateValues(_pickerStateDetails); - } - } - } - break; - case DateRangePickerSelectionMode.multiple: - { - if (_pickerStateDetails._selectedDates != null && - _pickerStateDetails._selectedDates.isNotEmpty) { - final List indexList = []; - for (int i = 0; - i < _pickerStateDetails._selectedDates.length; - i++) { - final bool isDisabled = - _isDisabledDate(_pickerStateDetails._selectedDates[i]); - if (isDisabled) { - indexList.add(i); - } - } - - if (indexList != null && indexList.isNotEmpty) { - for (int i = indexList.length - 1; i >= 0; i--) { - _pickerStateDetails._selectedDates.removeAt(indexList[i]); - } - - widget.updatePickerStateValues(_pickerStateDetails); - } - } - } - break; - case DateRangePickerSelectionMode.range: - { - if (_pickerStateDetails._selectedRange != null) { - final bool isDisabled = - _isDisabledRange(_pickerStateDetails._selectedRange); - if (isDisabled) { - _pickerStateDetails._selectedRange = null; - } - - widget.updatePickerStateValues(_pickerStateDetails); - } - } - break; - case DateRangePickerSelectionMode.multiRange: - { - if (_pickerStateDetails._selectedRanges != null && - _pickerStateDetails._selectedRanges.isNotEmpty) { - final List indexList = []; - for (int i = 0; - i < _pickerStateDetails._selectedRanges.length; - i++) { - final bool isDisabled = - _isDisabledRange(_pickerStateDetails._selectedRanges[i]); - if (isDisabled) { - indexList.add(i); - } - } - - if (indexList != null && indexList.isNotEmpty) { - for (int i = indexList.length - 1; i >= 0; i--) { - _pickerStateDetails._selectedRanges.removeAt(indexList[i]); - } - - widget.updatePickerStateValues(_pickerStateDetails); - } - } - } - } - } - - void _moveToNextViewWithAnimation() { - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (_animationController.isCompleted || _animationController.isDismissed) { - _animationController.reset(); - } else { - return; - } - - _updateSelection(); - if (widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical) { - // update the bottom to top swiping - _tween.begin = 0; - _tween.end = -widget.height; - } else { - // update the right to left swiping - _tween.begin = 0; - _tween.end = -widget.width; - } - - _animationController.duration = const Duration(milliseconds: 500); - _animationController - .forward() - .then((dynamic value) => _updateNextView()); - - /// updates the current view visible dates when the view swiped - _updateCurrentViewVisibleDates(isNextView: true); - } - - void _moveToPreviousViewWithAnimation() { - // Resets the controller to backward it again, the animation will backward - // only from the dismissed state - if (_animationController.isCompleted || _animationController.isDismissed) { - _animationController.reset(); - } else { - return; - } - - _updateSelection(); - if (widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical) { - // update the top to bottom swiping - _tween.begin = 0; - _tween.end = widget.height; - } else { - // update the left to right swiping - _tween.begin = 0; - _tween.end = widget.width; - } - - _animationController.duration = const Duration(milliseconds: 500); - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); - - /// updates the current view visible dates when the view swiped. - _updateCurrentViewVisibleDates(); - } - - /// Update the selection details to scroll view children except current view - /// while view navigation. - void _updateSelection() { - /// Update selection on month view and update selection on year view when - /// [allowViewNavigation] property on [SfDateRangePicker] as false - if (widget.controller.view != DateRangePickerView.month && - widget.picker.allowViewNavigation) { - return; - } - - widget.getPickerStateValues(_pickerStateDetails); - for (int i = 0; i < _children.length; i++) { - if (i == _currentChildIndex) { - continue; - } - - final _PickerViewState viewState = _getCurrentViewState(i); - switch (widget.controller.view) { - case DateRangePickerView.month: - { - viewState._monthView._updateSelection(_pickerStateDetails); - } - break; - case DateRangePickerView.year: - case DateRangePickerView.decade: - case DateRangePickerView.century: - { - viewState._yearView._updateSelection(_pickerStateDetails); - } - } - } - } - - /// Draw the selection on current month view when selected date value - /// changed dynamically. - void _drawMonthSelection() { - if (widget.controller.view != DateRangePickerView.month || - _children.isEmpty) { - return; - } - - for (int i = 0; i < _children.length; i++) { - final _PickerViewState viewState = _getCurrentViewState(i); - - /// Check the visible dates rather than current child index because - /// current child index value not updated when the selected date value - /// changed on view changed callback - if (viewState == null || - viewState._monthView.visibleDates != - _pickerStateDetails._currentViewVisibleDates) { - continue; - } - - viewState._monthView._updateSelection(_pickerStateDetails); - } - } - - /// Draw the selection on current year, decade, century view when - /// selected date value changed dynamically. - void _drawYearSelection() { - if (widget.controller.view == DateRangePickerView.month || - _children.isEmpty) { - return; - } - - for (int i = 0; i < _children.length; i++) { - final _PickerViewState viewState = _getCurrentViewState(i); - - /// Check the visible dates rather than current child index because - /// current child index value not updated when the selected date value - /// changed on view changed callback - if (viewState == null || - viewState._yearView.visibleDates != - _pickerStateDetails._currentViewVisibleDates) { - continue; - } - - viewState._yearView._updateSelection(_pickerStateDetails); - } - } - - /// Return the picker view state details based on view index. - _PickerViewState _getCurrentViewState(int index) { - if (index == 1) { - return _currentViewKey.currentState; - } else if (index == 2) { - return _nextViewKey.currentState; - } - - return _previousViewKey.currentState; - } - - /// Updates the current view visible dates for calendar in the swiping end - void _updateCurrentViewVisibleDates({bool isNextView = false}) { - if (isNextView) { - if (_currentChildIndex == 0) { - _currentViewVisibleDates = _visibleDates; - } else if (_currentChildIndex == 1) { - _currentViewVisibleDates = _nextViewVisibleDates; - } else { - _currentViewVisibleDates = _previousViewVisibleDates; - } - } else { - if (_currentChildIndex == 0) { - _currentViewVisibleDates = _nextViewVisibleDates; - } else if (_currentChildIndex == 1) { - _currentViewVisibleDates = _previousViewVisibleDates; - } else { - _currentViewVisibleDates = _visibleDates; - } - } - - _pickerStateDetails._currentViewVisibleDates = _currentViewVisibleDates; - _pickerStateDetails._currentDate = _currentViewVisibleDates[0]; - if (widget.controller.view == DateRangePickerView.month && - widget.picker.monthViewSettings.numberOfWeeksInView == 6) { - final DateTime date = _currentViewVisibleDates[ - _currentViewVisibleDates.length ~/ - (widget.picker.enableMultiView ? 4 : 2)]; - _pickerStateDetails._currentDate = DateTime(date.year, date.month, 1); - } - - widget.updatePickerStateValues(_pickerStateDetails); - } - - void _updateNextView() { - if (!_animationController.isCompleted) { - return; - } - - _updateNextViewVisibleDates(); - - if (_currentChildIndex == 0) { - _currentChildIndex = 1; - } else if (_currentChildIndex == 1) { - _currentChildIndex = 2; - } else if (_currentChildIndex == 2) { - _currentChildIndex = 0; - } - - if (kIsWeb) { - setState(() { - /// set state called to call the build method to fix the date doesn't - /// update properly issue on web, in Andriod and iOS the build method - /// called automatically when the animation ends but in web it doesn't - /// work on that way, hence we have manually called the build method by - /// adding setstate and i have logged and issue in framework once i got - /// the solution will remove this setstate - }); - } - - _resetPosition(); - } - - void _updatePreviousView() { - if (!_animationController.isCompleted) { - return; - } - - _updatePreviousViewVisibleDates(); - - if (_currentChildIndex == 0) { - _currentChildIndex = 2; - } else if (_currentChildIndex == 1) { - _currentChildIndex = 0; - } else if (_currentChildIndex == 2) { - _currentChildIndex = 1; - } - - if (kIsWeb) { - setState(() { - /// set state called to call the build method to fix the date doesn't - /// update properly issue on web, in Andriod and iOS the build method - /// called automatically when the animation ends but in web it doesn't - /// work on that way, hence we have manually called the build method by - /// adding setstate and i have logged and issue in framework once i got - /// the solution will remove this setstate - }); - } - - _resetPosition(); - } - - // 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; - } - }); - } - - /// Calculate and return the date time value based on previous selected date, - /// keyboard action and current picker view. - DateTime _getYearSelectedDate(DateTime selectedDate, PhysicalKeyboardKey key, - _PickerView view, _PickerViewState state) { - DateTime date; - - /// Calculate the index value for previous selected date. - int index = - _getYearCellIndex(view.visibleDates, selectedDate, state._yearView); - if (key == PhysicalKeyboardKey.arrowRight) { - /// If index value as last cell index in current view then - /// navigate to next view. Calculate the selected index on navigated view - /// and return the selected date on navigated view on right arrow pressed - /// action. - if ((index == view.visibleDates.length - 1 || - (widget.picker.enableMultiView && - widget.controller.view != DateRangePickerView.year && - index >= view.visibleDates.length - 3)) && - widget.picker.selectionMode == DateRangePickerSelectionMode.single) { - widget.isRtl - ? _moveToPreviousViewWithAnimation() - : _moveToNextViewWithAnimation(); - } - - if (index != -1) { - date = _getNextDate(widget.controller.view, selectedDate); - } - } else if (key == PhysicalKeyboardKey.arrowLeft) { - /// If index value as first cell index in current view then - /// navigate to previous view. Calculate the selected index on navigated - /// view and return the selected date on navigated view on left arrow - /// pressed action. - if (index == 0 && - widget.picker.selectionMode == DateRangePickerSelectionMode.single) { - widget.isRtl - ? _moveToNextViewWithAnimation() - : _moveToPreviousViewWithAnimation(); - } - - if (index != -1) { - date = _getPreviousDate(widget.controller.view, selectedDate); - } - } else if (key == PhysicalKeyboardKey.arrowUp) { - /// If index value not in first row then calculate the date by - /// subtracting the index value with 3 and return the date value. - if (index >= 3 && index != -1) { - index -= 3; - date = view.visibleDates[index]; - } - } else if (key == PhysicalKeyboardKey.arrowDown) { - /// If index value not in last row then calculate the date by - /// adding the index value with 3 and return the date value. - if (index <= 8 && index != -1) { - index += 3; - date = view.visibleDates[index]; - } - } - - return date; - } - - void _switchViewsByKeyBoardEvent(RawKeyEvent 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 (event.physicalKey == PhysicalKeyboardKey.digit1) { - _pickerStateDetails._view = DateRangePickerView.month; - } else if (event.physicalKey == PhysicalKeyboardKey.digit2) { - _pickerStateDetails._view = DateRangePickerView.year; - } else if (event.physicalKey == PhysicalKeyboardKey.digit3) { - _pickerStateDetails._view = DateRangePickerView.decade; - } else if (event.physicalKey == PhysicalKeyboardKey.digit4) { - _pickerStateDetails._view = DateRangePickerView.century; - } - - widget.updatePickerStateValues(_pickerStateDetails); - return; - } - } - - void _updateYearSelectionByKeyBoardNavigation( - _PickerViewState currentVisibleViewState, - _PickerView currentVisibleView, - RawKeyEvent event) { - DateTime selectedDate; - if (_pickerStateDetails._selectedDate != null && - widget.picker.selectionMode == DateRangePickerSelectionMode.single) { - selectedDate = _getYearSelectedDate(_pickerStateDetails._selectedDate, - event.physicalKey, currentVisibleView, currentVisibleViewState); - if (selectedDate != null && - currentVisibleViewState._yearView - ._isBetweenMinMaxMonth(selectedDate)) { - _pickerStateDetails._selectedDate = selectedDate; - } - } else if (widget.picker.selectionMode == - DateRangePickerSelectionMode.multiple && - _pickerStateDetails._selectedDates != null && - _pickerStateDetails._selectedDates.isNotEmpty && - event.isShiftPressed) { - final DateTime date = _pickerStateDetails - ._selectedDates[_pickerStateDetails._selectedDates.length - 1]; - selectedDate = _getYearSelectedDate( - date, event.physicalKey, currentVisibleView, currentVisibleViewState); - if (selectedDate != null && - currentVisibleViewState._yearView - ._isBetweenMinMaxMonth(selectedDate)) { - _pickerStateDetails._selectedDates = - _cloneList(_pickerStateDetails._selectedDates)..add(selectedDate); - } - } - - widget.updatePickerStateValues(_pickerStateDetails); - _drawYearSelection(); - } - - void _updateSelectionByKeyboardNavigation(DateTime selectedDate) { - switch (widget.picker.selectionMode) { - case DateRangePickerSelectionMode.single: - { - _pickerStateDetails._selectedDate = selectedDate; - } - break; - case DateRangePickerSelectionMode.multiple: - { - _pickerStateDetails._selectedDates.add(selectedDate); - } - break; - case DateRangePickerSelectionMode.range: - { - if (_pickerStateDetails._selectedRange != null && - _pickerStateDetails._selectedRange.startDate != null && - (_pickerStateDetails._selectedRange.endDate == null || - isSameDate(_pickerStateDetails._selectedRange.startDate, - _pickerStateDetails._selectedRange.endDate))) { - _pickerStateDetails._selectedRange = PickerDateRange( - _pickerStateDetails._selectedRange.startDate, selectedDate); - } else { - _pickerStateDetails._selectedRange = - PickerDateRange(selectedDate, null); - } - } - break; - case DateRangePickerSelectionMode.multiRange: - break; - } - } - - void _onKeyDown(RawKeyEvent event) { - if (event.runtimeType != RawKeyDownEvent) { - return; - } - - _switchViewsByKeyBoardEvent(event); - - if (widget.controller.view != DateRangePickerView.month && - widget.picker.allowViewNavigation) { - return; - } - - if (_pickerStateDetails._selectedDate == null && - (_pickerStateDetails._selectedDates == null || - _pickerStateDetails._selectedDates.isEmpty) && - _pickerStateDetails._selectedRange == null && - (_pickerStateDetails._selectedRanges == null || - _pickerStateDetails._selectedRanges.isEmpty)) { - return; - } - - _PickerViewState currentVisibleViewState; - _PickerView currentVisibleView; - if (_currentChildIndex == 0) { - currentVisibleViewState = _previousViewKey.currentState; - currentVisibleView = _previousView; - } else if (_currentChildIndex == 1) { - currentVisibleViewState = _currentViewKey.currentState; - currentVisibleView = _currentView; - } else if (_currentChildIndex == 2) { - currentVisibleViewState = _nextViewKey.currentState; - currentVisibleView = _nextView; - } - - if (widget.controller.view != DateRangePickerView.month) { - _updateYearSelectionByKeyBoardNavigation( - currentVisibleViewState, currentVisibleView, event); - return; - } - - final DateTime selectedDate = - _updateSelectedDate(event, currentVisibleViewState, currentVisibleView); - - if (_isDateWithInVisibleDates(currentVisibleView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, selectedDate) || - !_isEnabledDate(widget.picker.minDate, widget.picker.maxDate, - widget.picker.enablePastDates, selectedDate)) { - return; - } - - if (!_isDateAsCurrentMonthDate( - currentVisibleView.visibleDates[ - currentVisibleView.visibleDates.length ~/ - (widget.picker.enableMultiView ? 4 : 2)], - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.monthViewSettings.showTrailingAndLeadingDates, - selectedDate)) { - if (selectedDate.month == - getNextMonthDate(currentVisibleView.visibleDates[ - currentVisibleView.visibleDates.length ~/ - (widget.picker.enableMultiView ? 4 : 2)]) - .month) { - widget.isRtl - ? _moveToPreviousViewWithAnimation() - : _moveToNextViewWithAnimation(); - } else { - widget.isRtl - ? _moveToNextViewWithAnimation() - : _moveToPreviousViewWithAnimation(); - } - } - - currentVisibleViewState._drawSelection(selectedDate); - - _updateSelectionByKeyboardNavigation(selectedDate); - widget.updatePickerStateValues(_pickerStateDetails); - _updateSelection(); - } - - DateTime _updateSingleSelectionByKeyBoardKeys( - RawKeyEvent event, _PickerView currentView) { - if (event.physicalKey == PhysicalKeyboardKey.arrowRight) { - if (isSameDate(_pickerStateDetails._selectedDate, - currentView.visibleDates[currentView.visibleDates.length - 1])) { - _moveToNextViewWithAnimation(); - } - - return addDuration( - _pickerStateDetails._selectedDate, const Duration(days: 1)); - } else if (event.physicalKey == PhysicalKeyboardKey.arrowLeft) { - if (isSameDate( - _pickerStateDetails._selectedDate, currentView.visibleDates[0])) { - _moveToPreviousViewWithAnimation(); - } - - return subtractDuration( - _pickerStateDetails._selectedDate, const Duration(days: 1)); - } else if (event.physicalKey == PhysicalKeyboardKey.arrowUp) { - return subtractDuration(_pickerStateDetails._selectedDate, - const Duration(days: _kNumberOfDaysInWeek)); - } else if (event.physicalKey == PhysicalKeyboardKey.arrowDown) { - return addDuration(_pickerStateDetails._selectedDate, - const Duration(days: _kNumberOfDaysInWeek)); - } - return null; - } - - DateTime _updateMultiAndRangeSelectionByKeyBoard(RawKeyEvent event) { - if (event.isShiftPressed && - event.physicalKey == PhysicalKeyboardKey.arrowRight) { - if (widget.picker.selectionMode == - DateRangePickerSelectionMode.multiple) { - return addDuration( - _pickerStateDetails - ._selectedDates[_pickerStateDetails._selectedDates.length - 1], - const Duration(days: 1)); - } else { - return addDuration(_pickerStateDetails._selectedRange.startDate, - const Duration(days: 1)); - } - } else if (event.isShiftPressed && - event.physicalKey == PhysicalKeyboardKey.arrowLeft) { - if (widget.picker.selectionMode == - DateRangePickerSelectionMode.multiple) { - return addDuration( - _pickerStateDetails - ._selectedDates[_pickerStateDetails._selectedDates.length - 1], - const Duration(days: -1)); - } else { - return addDuration(_pickerStateDetails._selectedRange.startDate, - const Duration(days: -1)); - } - } else if (event.isShiftPressed && - event.physicalKey == PhysicalKeyboardKey.arrowUp) { - if (widget.picker.selectionMode == - DateRangePickerSelectionMode.multiple) { - return addDuration( - _pickerStateDetails - ._selectedDates[_pickerStateDetails._selectedDates.length - 1], - const Duration(days: -_kNumberOfDaysInWeek)); - } else { - return addDuration(_pickerStateDetails._selectedRange.startDate, - const Duration(days: -_kNumberOfDaysInWeek)); - } - } else if (event.isShiftPressed && - event.physicalKey == PhysicalKeyboardKey.arrowDown) { - if (widget.picker.selectionMode == - DateRangePickerSelectionMode.multiple) { - return addDuration( - _pickerStateDetails - ._selectedDates[_pickerStateDetails._selectedDates.length - 1], - const Duration(days: _kNumberOfDaysInWeek)); - } else { - return addDuration(_pickerStateDetails._selectedRange.startDate, - const Duration(days: _kNumberOfDaysInWeek)); - } - } - return null; - } - - DateTime _updateSelectedDate(RawKeyEvent event, _PickerViewState currentState, - _PickerView currentView) { - switch (widget.picker.selectionMode) { - case DateRangePickerSelectionMode.single: - { - return _updateSingleSelectionByKeyBoardKeys(event, currentView); - } - case DateRangePickerSelectionMode.multiple: - case DateRangePickerSelectionMode.range: - { - return _updateMultiAndRangeSelectionByKeyBoard(event); - } - case DateRangePickerSelectionMode.multiRange: - break; - } - - return null; - } - - void _onHorizontalStart(DragStartDetails dragStartDetails) { - switch (widget.picker.navigationDirection) { - case DateRangePickerNavigationDirection.horizontal: - { - _scrollStartPosition = dragStartDetails.globalPosition.dx; - _updateSelection(); - } - break; - case DateRangePickerNavigationDirection.vertical: - break; - } - } - - void _onHorizontalUpdate(DragUpdateDetails dragUpdateDetails) { - switch (widget.picker.navigationDirection) { - case DateRangePickerNavigationDirection.horizontal: - { - final double difference = - dragUpdateDetails.globalPosition.dx - _scrollStartPosition; - if (difference < 0 && - !_canMoveToNextViewRtl( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.minDate, - widget.picker.maxDate, - _currentViewVisibleDates, - widget.isRtl, - widget.picker.enableMultiView)) { - return; - } else if (difference > 0 && - !_canMoveToPreviousViewRtl( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.minDate, - widget.picker.maxDate, - _currentViewVisibleDates, - widget.isRtl, - widget.picker.enableMultiView)) { - return; - } - - _position = difference; - setState(() { - /* Updates the widget navigated distance and moves the widget - in the custom scroll view */ - }); - } - break; - case DateRangePickerNavigationDirection.vertical: - break; - } - } - - void _onHorizontalEnd(DragEndDetails dragEndDetails) { - switch (widget.picker.navigationDirection) { - case DateRangePickerNavigationDirection.vertical: - break; - case DateRangePickerNavigationDirection.horizontal: - { - _position ??= 0; - // condition to check and update the right to left swiping - if (-_position >= widget.width / 2) { - _tween.begin = _position; - _tween.end = -widget.width; - - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted && _position != _tween.end) { - _animationController.reset(); - } - - _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updateNextView()); - - /// updates the current view visible dates when the view swiped in - /// right to left direction - _updateCurrentViewVisibleDates(isNextView: true); - } - // fling the view from right to left - else if (-dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { - if (!_canMoveToNextViewRtl( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.minDate, - widget.picker.maxDate, - _currentViewVisibleDates, - widget.isRtl, - widget.picker.enableMultiView)) { - _position = 0; - setState(() { - /* Completes the swiping and rearrange the children position in - the custom scroll view */ - }); - return; - } - - _tween.begin = _position; - _tween.end = -widget.width; - - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted && _position != _tween.end) { - _animationController.reset(); - } - - _animationController.duration = const Duration(milliseconds: 250); - _animationController - .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) - .then((dynamic value) => _updateNextView()); - - /// updates the current view visible dates when fling the view in - /// right to left direction - _updateCurrentViewVisibleDates(isNextView: true); - } - // condition to check and update the left to right swiping - else if (_position >= widget.width / 2) { - _tween.begin = _position; - _tween.end = widget.width; - - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } - - _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); - - /// updates the current view visible dates when the view swiped in - /// left to right direction - _updateCurrentViewVisibleDates(); - } - // fling the view from left to right - else if (dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { - if (!_canMoveToPreviousViewRtl( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.minDate, - widget.picker.maxDate, - _currentViewVisibleDates, - widget.isRtl, - widget.picker.enableMultiView)) { - _position = 0; - setState(() { - /* Completes the swiping and rearrange the children position in - the custom scroll view */ - }); - return; - } - - _tween.begin = _position; - _tween.end = widget.width; - - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted && _position != _tween.end) { - _animationController.reset(); - } - - _animationController.duration = const Duration(milliseconds: 250); - _animationController - .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) - .then((dynamic value) => _updatePreviousView()); - - /// updates the current view visible dates when fling the view in - /// left to right direction - _updateCurrentViewVisibleDates(); - } - // condition to check and revert the right to left swiping - else if (_position.abs() <= widget.width / 2) { - _tween.begin = _position; - _tween.end = 0.0; - - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted && _position != _tween.end) { - _animationController.reset(); - } - - _animationController.duration = const Duration(milliseconds: 250); - _animationController.forward(); - } - } - } - } - - void _onVerticalStart(DragStartDetails dragStartDetails) { - switch (widget.picker.navigationDirection) { - case DateRangePickerNavigationDirection.horizontal: - break; - case DateRangePickerNavigationDirection.vertical: - { - _scrollStartPosition = dragStartDetails.globalPosition.dy; - } - break; - } - } - - void _onVerticalUpdate(DragUpdateDetails dragUpdateDetails) { - switch (widget.picker.navigationDirection) { - case DateRangePickerNavigationDirection.horizontal: - break; - case DateRangePickerNavigationDirection.vertical: - { - final double difference = - dragUpdateDetails.globalPosition.dy - _scrollStartPosition; - if (difference < 0 && - !_canMoveToNextView( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.maxDate, - _currentViewVisibleDates, - widget.picker.enableMultiView)) { - return; - } else if (difference > 0 && - !_canMoveToPreviousView( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.minDate, - _currentViewVisibleDates, - widget.picker.enableMultiView)) { - return; - } - - _position = difference; - setState(() { - /* Updates the widget navigated distance and moves the widget - in the custom scroll view */ - }); - } - } - } - - void _onVerticalEnd(DragEndDetails dragEndDetails) { - switch (widget.picker.navigationDirection) { - case DateRangePickerNavigationDirection.horizontal: - break; - case DateRangePickerNavigationDirection.vertical: - { - _position ??= 0; - // condition to check and update the bottom to top swiping - if (-_position >= widget.height / 2) { - _tween.begin = _position; - _tween.end = -widget.height; - - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } - - _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updateNextView()); - - /// updates the current view visible dates when the view swiped in - /// bottom to top direction - _updateCurrentViewVisibleDates(isNextView: true); - } - // fling the view to bottom to top - else if (-dragEndDetails.velocity.pixelsPerSecond.dy > - widget.height) { - if (!_canMoveToNextView( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.maxDate, - _currentViewVisibleDates, - widget.picker.enableMultiView)) { - _position = 0; - setState(() { - /* Completes the swiping and rearrange the children position in - the custom scroll view */ - }); - return; - } - _tween.begin = _position; - _tween.end = -widget.height; - - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } - - _animationController.duration = const Duration(milliseconds: 250); - _animationController - .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) - .then((dynamic value) => _updateNextView()); - - /// updates the current view visible dates when fling the view in - /// bottom to top direction - _updateCurrentViewVisibleDates(isNextView: true); - } - // condition to check and update the top to bottom swiping - else if (_position >= widget.height / 2) { - _tween.begin = _position; - _tween.end = widget.height; - - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } - - _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); - - /// updates the current view visible dates when the view swiped in - /// top to bottom direction - _updateCurrentViewVisibleDates(); - } - // fling the view to top to bottom - else if (dragEndDetails.velocity.pixelsPerSecond.dy > widget.height) { - if (!_canMoveToPreviousView( - widget.controller.view, - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.minDate, - _currentViewVisibleDates, - widget.picker.enableMultiView)) { - _position = 0; - setState(() { - /* Completes the swiping and rearrange the children position in - the custom scroll view */ - }); - return; - } - - _tween.begin = _position; - _tween.end = widget.height; - - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } - - _animationController.duration = const Duration(milliseconds: 250); - _animationController - .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) - .then((dynamic value) => _updatePreviousView()); - - /// updates the current view visible dates when fling the view in - /// top to bottom direction - _updateCurrentViewVisibleDates(); - } - // condition to check and revert the bottom to top swiping - else if (_position.abs() <= widget.height / 2) { - _tween.begin = _position; - _tween.end = 0.0; - - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } - - _animationController.duration = const Duration(milliseconds: 250); - _animationController.forward(); - } - } - } - } -} 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 new file mode 100644 index 000000000..58ce98954 --- /dev/null +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view.dart @@ -0,0 +1,3638 @@ +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' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import '../../datepicker.dart'; +import 'date_picker_manager.dart'; +import 'picker_helper.dart'; + +/// Used to hold the month cell widgets. +class MonthView extends StatefulWidget { + /// Constructor for create the month view widget used to hold the month cell + /// widgets. + 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); + + /// Defines the month row count. + final int rowCount; + + /// Defines the month cell style. + final dynamic cellStyle; + + /// Holds the visible dates for the month view. + final List visibleDates; + + /// Used to identify the widget direction is RTL. + final bool isRtl; + + /// Defines the today cell highlight color. + final Color todayHighlightColor; + + /// Holds the theme data for date range picker. + final SfDateRangePickerThemeData datePickerTheme; + + /// The minimum date as much as the [SfDateRangePicker] will navigate. + final dynamic minDate; + + /// The maximum date as much as the [SfDateRangePicker] will navigate. + final dynamic maxDate; + + /// Decides to enable past dates or not. + final bool enablePastDates; + + /// Decides the trailing and leading of month view will visible or not. + final bool showLeadingAndTailingDates; + + /// Holds the blackout dates of the [SfDateRangePicker]. + final List blackoutDates; + + /// Holds the special dates of the [SfDateRangePicker]. + final List specialDates; + + /// Holds the list of week day index of the [SfDateRangePicker]. + final List weekendDays; + + /// Decides the month cell highlight and selection shape. + final DateRangePickerSelectionShape selectionShape; + + /// Holds the selection radius of the month cell. + final double selectionRadius; + + /// Used to call repaint when the selection changes. + final ValueNotifier selectionNotifier; + + /// Used to specify the mouse hover position of the month view. + final ValueNotifier mouseHoverPosition; + + /// Decides to show the multi view of month view or not. + final bool enableMultiView; + + /// Specifies the space between the multi month views. + final double multiViewSpacing; + + /// Defines the text style for selected month cell. + final TextStyle selectionTextStyle; + + /// Defines the range text style for selected range month cell. + final TextStyle rangeTextStyle; + + /// Defines the background color for selected month cell. + final Color selectionColor; + + /// Defines the background color for selected range start date month cell. + final Color startRangeSelectionColor; + + /// Defines the background color for selected range end date month cell. + final Color endRangeSelectionColor; + + /// Defines the background color for selected range in between dates cell. + final Color rangeSelectionColor; + + /// Defines the text scale factor of [SfDateRangePicker]. + final double textScaleFactor; + + /// Defines the selection mode of the [SfDateRangePicker]. + final DateRangePickerSelectionMode selectionMode; + + /// Defines the height of the month view. + final double height; + + /// Defines the width of the month view. + final double width; + + /// Used to get the picker state details from picker view widget. + final UpdatePickerState getPickerStateDetails; + + /// Used to build the widget that replaces the month cells in month view. + final DateRangePickerCellBuilder cellBuilder; + + /// Specifies the pickerType for [SfDateRangePicker]. + final bool isHijri; + + /// Specifies the localizations. + final SfLocalizations localizations; + + /// Defines the navigation direction for [SfDateRangePicker]. + final DateRangePickerNavigationDirection navigationDirection; + + @override + _MonthViewState createState() => _MonthViewState(); +} + +class _MonthViewState extends State { + PickerStateArgs _pickerStateDetails; + dynamic _selectedDate; + List _selectedDates; + dynamic _selectedRange; + List _selectedRanges; + List _children; + + @override + void initState() { + _pickerStateDetails = PickerStateArgs(); + widget.getPickerStateDetails(_pickerStateDetails); + _selectedDate = _pickerStateDetails.selectedDate; + _selectedDates = + DateRangePickerHelper.cloneList(_pickerStateDetails.selectedDates); + _selectedRange = _pickerStateDetails.selectedRange; + _selectedRanges = + DateRangePickerHelper.cloneList(_pickerStateDetails.selectedRanges); + widget.selectionNotifier.addListener(_updateSelection); + super.initState(); + } + + @override + void didUpdateWidget(MonthView oldWidget) { + if (widget.height != oldWidget.height || + widget.width != oldWidget.width || + widget.enablePastDates != oldWidget.enablePastDates || + widget.minDate != oldWidget.minDate || + widget.maxDate != oldWidget.maxDate || + widget.cellBuilder != oldWidget.cellBuilder || + widget.selectionMode != oldWidget.selectionMode || + widget.multiViewSpacing != oldWidget.multiViewSpacing || + widget.enableMultiView != oldWidget.enableMultiView || + !DateRangePickerHelper.isDateCollectionEquals( + widget.blackoutDates, oldWidget.blackoutDates) || + !DateRangePickerHelper.isDateCollectionEquals( + widget.specialDates, oldWidget.specialDates) || + widget.showLeadingAndTailingDates != + oldWidget.showLeadingAndTailingDates || + widget.rowCount != oldWidget.rowCount || + widget.localizations != oldWidget.localizations || + widget.isHijri != oldWidget.isHijri || + widget.navigationDirection != oldWidget.navigationDirection || + widget.visibleDates != oldWidget.visibleDates) { + _children.clear(); + } + + if (widget.selectionNotifier != oldWidget.selectionNotifier) { + oldWidget.selectionNotifier.removeListener(_updateSelection); + widget.selectionNotifier.addListener(_updateSelection); + } + + _updateSelection(isNeedSetState: false); + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + widget.selectionNotifier.removeListener(_updateSelection); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + _children ??= []; + if (widget.cellBuilder != null && _children.isEmpty) { + double webUIPadding = 0; + double width = widget.width; + double height = widget.height; + int viewCount = 1; + final bool isHorizontalMultiView = widget.enableMultiView && + widget.navigationDirection == + DateRangePickerNavigationDirection.horizontal; + final bool isVerticalMultiView = widget.enableMultiView && + widget.navigationDirection == + DateRangePickerNavigationDirection.vertical; + + if (isHorizontalMultiView) { + webUIPadding = widget.multiViewSpacing; + viewCount = 2; + width = (width - webUIPadding) / viewCount; + } else if (isVerticalMultiView) { + webUIPadding = widget.multiViewSpacing; + viewCount = 2; + height = (height - webUIPadding) / viewCount; + } + + final int datesCount = widget.visibleDates.length ~/ viewCount; + final double cellWidth = width / DateTime.daysPerWeek; + final double cellHeight = height / widget.rowCount; + final bool hideLeadingAndTrailingDates = + (widget.rowCount == 6 && !widget.showLeadingAndTailingDates) || + 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; + 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); + 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) + + (rowIndex * DateTime.daysPerWeek); + } + + currentIndex += viewStartIndex; + + /// Check the x position reaches view end position then draw the + /// date on next cell. + /// Padding 1 value used to avoid decimal value difference. + /// eg., if view end position as 243 and x position as 242.499 then + /// round method in decimal return 242 rather than 243, so it does + /// not move the next line for draw date value. + if (xPosition + 1 >= viewEndPosition) { + xPosition = viewStartPosition; + yPosition += cellHeight; + } + + final dynamic date = widget.visibleDates[currentIndex]; + if (hideLeadingAndTrailingDates && date.month != currentMonth) { + xPosition += cellWidth; + continue; + } + + final DateRangePickerCellDetails cellDetails = + DateRangePickerCellDetails( + date: date, + visibleDates: widget.visibleDates, + bounds: Rect.fromLTWH( + xPosition, yPosition, cellWidth, cellHeight)); + final Widget child = widget.cellBuilder(context, cellDetails); + assert(child != null, 'Widget must not be null'); + _children.add(child); + xPosition += cellWidth; + } + } + } + + return _getMonthRenderWidget(); + } + + void _updateSelection({bool isNeedSetState = true}) { + widget.getPickerStateDetails(_pickerStateDetails); + if (_isSelectedValueEquals()) { + return; + } + + _children.clear(); + _selectedDate = _pickerStateDetails.selectedDate; + _selectedDates = + DateRangePickerHelper.cloneList(_pickerStateDetails.selectedDates); + _selectedRange = _pickerStateDetails.selectedRange; + _selectedRanges = + DateRangePickerHelper.cloneList(_pickerStateDetails.selectedRanges); + + if (!isNeedSetState) { + return; + } + + setState(() { + /// Update the state while selection notifier value and does not update + /// the state while did update widget call this method. + }); + } + + bool _isSelectedValueEquals() { + switch (widget.selectionMode) { + case DateRangePickerSelectionMode.single: + { + return isSameDate(_selectedDate, _pickerStateDetails.selectedDate); + } + case DateRangePickerSelectionMode.multiple: + { + return DateRangePickerHelper.isDateCollectionEquals( + _selectedDates, _pickerStateDetails.selectedDates); + } + case DateRangePickerSelectionMode.range: + { + return DateRangePickerHelper.isRangeEquals( + _selectedRange, _pickerStateDetails.selectedRange); + } + case DateRangePickerSelectionMode.multiRange: + { + return DateRangePickerHelper.isDateRangesEquals( + _selectedRanges, _pickerStateDetails.selectedRanges); + } + } + return false; + } + + MultiChildRenderObjectWidget _getMonthRenderWidget() { + switch (widget.selectionMode) { + 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, + 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, + 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, + 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, + widgets: _children); + } + } + + return null; + } +} + +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, + {List widgets}) + : super(children: widgets); + + final int rowCount; + + final dynamic cellStyle; + + final List visibleDates; + + final bool isRtl; + + final Color todayHighlightColor; + + final SfDateRangePickerThemeData datePickerTheme; + + final dynamic minDate; + + final dynamic maxDate; + + final DateRangePickerNavigationDirection navigationDirection; + + final bool enablePastDates; + + final bool showLeadingAndTailingDates; + + final List blackoutDates; + + final List specialDates; + + final List weekendDays; + + final DateRangePickerSelectionShape selectionShape; + + final double selectionRadius; + + final ValueNotifier selectionNotifier; + + final ValueNotifier mouseHoverPosition; + + final bool enableMultiView; + + final double multiViewSpacing; + + final TextStyle selectionTextStyle; + + final TextStyle rangeTextStyle; + + final Color selectionColor; + + final Color startRangeSelectionColor; + + final Color endRangeSelectionColor; + + final Color rangeSelectionColor; + + final double textScaleFactor; + + final double height; + + final double width; + + final dynamic selectedDate; + + final bool isHijri; + + final SfLocalizations localizations; + + @override + _MonthViewSingleSelectionRenderObject createRenderObject( + 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); + } + + @override + void updateRenderObject(BuildContext context, + _MonthViewSingleSelectionRenderObject renderObject) { + renderObject + ..visibleDates = visibleDates + ..rowCount = rowCount + ..cellStyle = cellStyle + ..selectionTextStyle = selectionTextStyle + ..rangeTextStyle = rangeTextStyle + ..selectionColor = selectionColor + ..startRangeSelectionColor = startRangeSelectionColor + ..endRangeSelectionColor = endRangeSelectionColor + ..rangeSelectionColor = rangeSelectionColor + ..datePickerTheme = datePickerTheme + ..isRtl = isRtl + ..todayHighlightColor = todayHighlightColor + ..minDate = minDate + ..maxDate = maxDate + ..enablePastDates = enablePastDates + ..showLeadingAndTailingDates = showLeadingAndTailingDates + ..blackoutDates = blackoutDates + ..specialDates = specialDates + ..weekendDays = weekendDays + ..selectionShape = selectionShape + ..selectionRadius = selectionRadius + ..mouseHoverPosition = mouseHoverPosition + ..enableMultiView = enableMultiView + ..multiViewSpacing = multiViewSpacing + ..selectionNotifier = selectionNotifier + ..textScaleFactor = textScaleFactor + ..height = height + ..width = width + ..isHijri = isHijri + ..localizations = localizations + ..navigationDirection = navigationDirection + ..selectedDate = selectedDate; + } +} + +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, + {List widgets}) + : super(children: widgets); + + final int rowCount; + + final dynamic cellStyle; + + final List visibleDates; + + final bool isRtl; + + final Color todayHighlightColor; + + final SfDateRangePickerThemeData datePickerTheme; + + final dynamic minDate; + + final dynamic maxDate; + + final DateRangePickerNavigationDirection navigationDirection; + + final bool enablePastDates; + + final bool showLeadingAndTailingDates; + + final List blackoutDates; + + final List specialDates; + + final List weekendDays; + + final DateRangePickerSelectionShape selectionShape; + + final double selectionRadius; + + final ValueNotifier selectionNotifier; + + final ValueNotifier mouseHoverPosition; + + final bool enableMultiView; + + final double multiViewSpacing; + + final TextStyle selectionTextStyle; + + final TextStyle rangeTextStyle; + + final Color selectionColor; + + final Color startRangeSelectionColor; + + final Color endRangeSelectionColor; + + final Color rangeSelectionColor; + + final double textScaleFactor; + + final double height; + + final double width; + + final List selectedDates; + + final bool isHijri; + + final SfLocalizations localizations; + + @override + _MonthViewMultiSelectionRenderObject createRenderObject( + 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); + } + + @override + void updateRenderObject( + BuildContext context, _MonthViewMultiSelectionRenderObject renderObject) { + renderObject + ..visibleDates = visibleDates + ..rowCount = rowCount + ..cellStyle = cellStyle + ..selectionTextStyle = selectionTextStyle + ..rangeTextStyle = rangeTextStyle + ..selectionColor = selectionColor + ..startRangeSelectionColor = startRangeSelectionColor + ..endRangeSelectionColor = endRangeSelectionColor + ..rangeSelectionColor = rangeSelectionColor + ..datePickerTheme = datePickerTheme + ..isRtl = isRtl + ..todayHighlightColor = todayHighlightColor + ..minDate = minDate + ..maxDate = maxDate + ..enablePastDates = enablePastDates + ..showLeadingAndTailingDates = showLeadingAndTailingDates + ..blackoutDates = blackoutDates + ..specialDates = specialDates + ..weekendDays = weekendDays + ..selectionShape = selectionShape + ..selectionRadius = selectionRadius + ..mouseHoverPosition = mouseHoverPosition + ..enableMultiView = enableMultiView + ..multiViewSpacing = multiViewSpacing + ..selectionNotifier = selectionNotifier + ..textScaleFactor = textScaleFactor + ..height = height + ..width = width + ..isHijri = isHijri + ..localizations = localizations + ..navigationDirection = navigationDirection + ..selectedDates = selectedDates; + } +} + +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, + {List widgets}) + : super(children: widgets); + + final int rowCount; + + final dynamic cellStyle; + + final List visibleDates; + + final bool isRtl; + + final Color todayHighlightColor; + + final SfDateRangePickerThemeData datePickerTheme; + + final dynamic minDate; + + final dynamic maxDate; + + final DateRangePickerNavigationDirection navigationDirection; + + final bool enablePastDates; + + final bool showLeadingAndTailingDates; + + final List blackoutDates; + + final List specialDates; + + final List weekendDays; + + final DateRangePickerSelectionShape selectionShape; + + final double selectionRadius; + + final ValueNotifier selectionNotifier; + + final ValueNotifier mouseHoverPosition; + + final bool enableMultiView; + + final double multiViewSpacing; + + final TextStyle selectionTextStyle; + + final TextStyle rangeTextStyle; + + final Color selectionColor; + + final Color startRangeSelectionColor; + + final Color endRangeSelectionColor; + + final Color rangeSelectionColor; + + final double textScaleFactor; + + final double height; + + final double width; + + final dynamic selectedRange; + + final bool isHijri; + + final SfLocalizations localizations; + + @override + _MonthViewRangeSelectionRenderObject createRenderObject( + 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); + } + + @override + void updateRenderObject( + BuildContext context, _MonthViewRangeSelectionRenderObject renderObject) { + renderObject + ..visibleDates = visibleDates + ..rowCount = rowCount + ..cellStyle = cellStyle + ..selectionTextStyle = selectionTextStyle + ..rangeTextStyle = rangeTextStyle + ..selectionColor = selectionColor + ..startRangeSelectionColor = startRangeSelectionColor + ..endRangeSelectionColor = endRangeSelectionColor + ..rangeSelectionColor = rangeSelectionColor + ..datePickerTheme = datePickerTheme + ..isRtl = isRtl + ..todayHighlightColor = todayHighlightColor + ..minDate = minDate + ..maxDate = maxDate + ..enablePastDates = enablePastDates + ..showLeadingAndTailingDates = showLeadingAndTailingDates + ..blackoutDates = blackoutDates + ..specialDates = specialDates + ..weekendDays = weekendDays + ..selectionShape = selectionShape + ..selectionRadius = selectionRadius + ..mouseHoverPosition = mouseHoverPosition + ..enableMultiView = enableMultiView + ..multiViewSpacing = multiViewSpacing + ..selectionNotifier = selectionNotifier + ..textScaleFactor = textScaleFactor + ..height = height + ..width = width + ..isHijri = isHijri + ..localizations = localizations + ..navigationDirection = navigationDirection + ..selectedRange = selectedRange; + } +} + +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, + {List widgets}) + : super(children: widgets); + + final int rowCount; + + final dynamic cellStyle; + + final List visibleDates; + + final bool isRtl; + + final Color todayHighlightColor; + + final SfDateRangePickerThemeData datePickerTheme; + + final dynamic minDate; + + final dynamic maxDate; + + final bool enablePastDates; + + final bool showLeadingAndTailingDates; + + final List blackoutDates; + + final List specialDates; + + final List weekendDays; + + final DateRangePickerSelectionShape selectionShape; + + final double selectionRadius; + + final ValueNotifier selectionNotifier; + + final ValueNotifier mouseHoverPosition; + + final bool enableMultiView; + + final double multiViewSpacing; + + final TextStyle selectionTextStyle; + + final TextStyle rangeTextStyle; + + final Color selectionColor; + + final Color startRangeSelectionColor; + + final Color endRangeSelectionColor; + + final Color rangeSelectionColor; + + final double textScaleFactor; + + final double height; + + final double width; + + final List selectedRanges; + + final bool isHijri; + + final SfLocalizations localizations; + + final DateRangePickerNavigationDirection navigationDirection; + + @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); + } + + @override + void updateRenderObject(BuildContext context, + _MonthViewMultiRangeSelectionRenderObject renderObject) { + renderObject + ..visibleDates = visibleDates + ..rowCount = rowCount + ..cellStyle = cellStyle + ..selectionTextStyle = selectionTextStyle + ..rangeTextStyle = rangeTextStyle + ..selectionColor = selectionColor + ..startRangeSelectionColor = startRangeSelectionColor + ..endRangeSelectionColor = endRangeSelectionColor + ..rangeSelectionColor = rangeSelectionColor + ..datePickerTheme = datePickerTheme + ..isRtl = isRtl + ..todayHighlightColor = todayHighlightColor + ..minDate = minDate + ..maxDate = maxDate + ..enablePastDates = enablePastDates + ..showLeadingAndTailingDates = showLeadingAndTailingDates + ..blackoutDates = blackoutDates + ..specialDates = specialDates + ..weekendDays = weekendDays + ..selectionShape = selectionShape + ..selectionRadius = selectionRadius + ..mouseHoverPosition = mouseHoverPosition + ..enableMultiView = enableMultiView + ..multiViewSpacing = multiViewSpacing + ..selectionNotifier = selectionNotifier + ..textScaleFactor = textScaleFactor + ..height = height + ..width = width + ..isHijri = isHijri + ..localizations = localizations + ..navigationDirection = navigationDirection + ..selectedRanges = selectedRanges; + } +} + +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); + + DateRangePickerNavigationDirection _navigationDirection; + + DateRangePickerNavigationDirection get navigationDirection => + _navigationDirection; + + set navigationDirection(DateRangePickerNavigationDirection value) { + if (_navigationDirection == value) { + return; + } + + _navigationDirection = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Defines the month row count. + int _rowCount; + + int get rowCount => _rowCount; + + set rowCount(int value) { + if (_rowCount == value) { + return; + } + + _rowCount = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Holds the visible dates for the month view. + List _visibleDates; + + List get visibleDates => _visibleDates; + + set visibleDates(List value) { + if (_visibleDates == value) { + return; + } + + _visibleDates = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Defines the month cell style. + dynamic _cellStyle; + + dynamic get cellStyle => _cellStyle; + + set cellStyle(dynamic value) { + if (_cellStyle == value) { + return; + } + + _cellStyle = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + /// Used to identify the widget direction is RTL. + bool _isRtl; + + bool get isRtl => _isRtl; + + set isRtl(bool value) { + if (_isRtl == value) { + return; + } + + _isRtl = value; + markNeedsPaint(); + } + + /// Defines the today cell highlight color. + Color _todayHighlightColor; + + Color get todayHighlightColor => _todayHighlightColor; + + set todayHighlightColor(Color value) { + if (_todayHighlightColor == value) { + return; + } + + _todayHighlightColor = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + /// Holds the theme data for date range picker. + SfDateRangePickerThemeData _datePickerTheme; + + SfDateRangePickerThemeData get datePickerTheme => _datePickerTheme; + + set datePickerTheme(SfDateRangePickerThemeData value) { + if (_datePickerTheme == value) { + return; + } + + _datePickerTheme = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + /// The minimum date as much as the [SfDateRangePicker] will navigate. + dynamic _minDate; + + dynamic get minDate => _minDate; + + set minDate(dynamic value) { + if (_minDate == value) { + return; + } + + _minDate = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// The maximum date as much as the [SfDateRangePicker] will navigate. + dynamic _maxDate; + + dynamic get maxDate => _maxDate; + + set maxDate(dynamic value) { + if (_maxDate == value) { + return; + } + + _maxDate = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Decides to enable past dates or not. + bool _enablePastDates; + + bool get enablePastDates => _enablePastDates; + + set enablePastDates(bool value) { + if (_enablePastDates == value) { + return; + } + + _enablePastDates = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Decides the trailing and leading of month view will visible or not. + bool _showLeadingAndTailingDates; + + bool get showLeadingAndTailingDates => _showLeadingAndTailingDates; + + set showLeadingAndTailingDates(bool value) { + if (_showLeadingAndTailingDates == value) { + return; + } + + _showLeadingAndTailingDates = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Holds the blackout dates of the [SfDateRangePicker]. + List _blackoutDates; + + List get blackoutDates => _blackoutDates; + + set blackoutDates(List value) { + if (DateRangePickerHelper.isDateCollectionEquals(_blackoutDates, value)) { + return; + } + + _blackoutDates = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Holds the special dates of the [SfDateRangePicker]. + List _specialDates; + + List get specialDates => _specialDates; + + set specialDates(List value) { + if (DateRangePickerHelper.isDateCollectionEquals(_specialDates, value)) { + return; + } + + _specialDates = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Holds the list of week day index of the [SfDateRangePicker]. + List _weekendDays; + + List get weekendDays => _weekendDays; + + set weekendDays(List value) { + if (_weekendDays == value) { + return; + } + + _weekendDays = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + /// Decides the month cell highlight and selection shape. + DateRangePickerSelectionShape _selectionShape; + + DateRangePickerSelectionShape get selectionShape => _selectionShape; + + set selectionShape(DateRangePickerSelectionShape value) { + if (_selectionShape == value) { + return; + } + + _selectionShape = value; + markNeedsPaint(); + } + + /// Holds the selection radius of the month cell. + double _selectionRadius; + + double get selectionRadius => _selectionRadius; + + set selectionRadius(double value) { + if (_selectionRadius == value) { + return; + } + + _selectionRadius = value; + markNeedsPaint(); + } + + /// Used to call repaint when the selection changes. + ValueNotifier selectionNotifier; + + /// Used to specify the mouse hover position of the month view. + ValueNotifier _mouseHoverPosition; + + ValueNotifier get mouseHoverPosition => _mouseHoverPosition; + + set mouseHoverPosition(ValueNotifier value) { + if (_mouseHoverPosition == value) { + return; + } + + _mouseHoverPosition?.removeListener(markNeedsPaint); + _mouseHoverPosition = value; + _mouseHoverPosition?.addListener(markNeedsPaint); + markNeedsPaint(); + } + + /// Decides to show the multi view of month view or not. + bool _enableMultiView; + + bool get enableMultiView => _enableMultiView; + + set enableMultiView(bool value) { + if (_enableMultiView == value) { + return; + } + + _enableMultiView = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Specifies the space between the multi month views. + double _multiViewSpacing; + + double get multiViewSpacing => _multiViewSpacing; + + set multiViewSpacing(double value) { + if (_multiViewSpacing == value) { + return; + } + + _multiViewSpacing = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Defines the text style for selected month cell. + TextStyle _selectionTextStyle; + + TextStyle get selectionTextStyle => _selectionTextStyle; + + set selectionTextStyle(TextStyle value) { + if (_selectionTextStyle == value) { + return; + } + + _selectionTextStyle = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + /// Defines the range text style for selected range month cell. + TextStyle _rangeTextStyle; + + TextStyle get rangeTextStyle => _rangeTextStyle; + + set rangeTextStyle(TextStyle value) { + if (_rangeTextStyle == value) { + return; + } + + _rangeTextStyle = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + /// Defines the background color for selected month cell. + Color _selectionColor; + + Color get selectionColor => _selectionColor; + + set selectionColor(Color value) { + if (_selectionColor == value) { + return; + } + + _selectionColor = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + /// Defines the background color for selected range start date month cell. + Color _startRangeSelectionColor; + + Color get startRangeSelectionColor => _startRangeSelectionColor; + + set startRangeSelectionColor(Color value) { + if (_startRangeSelectionColor == value) { + return; + } + + _startRangeSelectionColor = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + /// Defines the background color for selected range end date month cell. + Color _endRangeSelectionColor; + + Color get endRangeSelectionColor => _endRangeSelectionColor; + + set endRangeSelectionColor(Color value) { + if (_endRangeSelectionColor == value) { + return; + } + + _endRangeSelectionColor = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + /// Defines the background color for selected range in between dates cell. + Color _rangeSelectionColor; + + Color get rangeSelectionColor => _rangeSelectionColor; + + set rangeSelectionColor(Color value) { + if (_rangeSelectionColor == value) { + return; + } + + _rangeSelectionColor = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + /// Defines the text scale factor of [SfDateRangePicker]. + double _textScaleFactor; + + double get textScaleFactor => _textScaleFactor; + + set textScaleFactor(double value) { + if (_textScaleFactor == value) { + return; + } + + _textScaleFactor = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + double _height; + + double get height => _height; + + set height(double value) { + if (_height == value) { + return; + } + + _height = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + double _width; + + double get width => _width; + + set width(double value) { + if (_width == value) { + return; + } + + _width = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + bool _isHijri; + + bool get isHijri => _isHijri; + + set isHijri(bool value) { + if (_isHijri == value) { + return; + } + + _isHijri = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + SfLocalizations localizations; + + /// Used to paint the selection of month cell on all the selection mode. + Paint _selectionPainter; + + /// Used to draw month cell text in month view. + TextPainter _textPainter; + + static const int _selectionPadding = 2; + + double _cellWidth, _cellHeight; + double _centerXPosition, _centerYPosition; + + /// attach will called when the render object rendered in view. + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _mouseHoverPosition?.addListener(markNeedsPaint); + } + + /// detach will called when the render object removed from view. + @override + void detach() { + _mouseHoverPosition?.removeListener(markNeedsPaint); + super.detach(); + } + + @override + void setupParentData(covariant RenderObject child) { + if (child.parentData is! _DatePickerParentData) { + child.parentData = _DatePickerParentData(); + } + } + + @override + void performLayout() { + final Size widgetSize = constraints.biggest; + size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height); + RenderBox child = firstChild; + if (child == null) { + return; + } + + double currentWidth = size.width; + double currentHeight = size.height; + if (_enableMultiView) { + if (_navigationDirection == + DateRangePickerNavigationDirection.horizontal) { + currentWidth = (currentWidth - multiViewSpacing) / 2; + } else { + currentHeight = (currentHeight - multiViewSpacing) / 2; + } + } + + final double cellWidth = currentWidth / DateTime.daysPerWeek; + final double cellHeight = currentHeight / rowCount; + while (child != null) { + child.layout(constraints.copyWith( + minHeight: cellHeight, + maxHeight: cellHeight, + minWidth: cellWidth, + maxWidth: cellWidth)); + child = childAfter(child); + } + } + + /// Update the selection details(date, dates, range, ranges) based on + /// [SfDateRangePicker] selection mode. + void updateSelection(PickerStateArgs details); + + /// Return the list of selected index based on it selection mode + /// value(date, dates, range, ranges). + List getSelectedIndexValues(int viewStartIndex, int viewEndIndex); + + /// 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); + + /// Draw the highlight for selected month cell based on it selection mode + /// value(date, dates, range, ranges) when the month cell have custom widget. + void drawCustomCellSelection(Canvas canvas, double x, double y, int index); + + @override + void paint(PaintingContext context, Offset offset); + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config.isSemanticBoundary = true; + } + + @override + void assembleSemanticsNode( + SemanticsNode node, + SemanticsConfiguration config, + Iterable children, + ) { + final List semantics = _getSemanticsBuilder(size); + final List semanticsNodes = []; + for (int i = 0; i < semantics.length; i++) { + final CustomPainterSemantics currentSemantics = semantics[i]; + final SemanticsNode newChild = SemanticsNode( + key: currentSemantics.key, + ); + + final SemanticsProperties properties = currentSemantics.properties; + final SemanticsConfiguration config = SemanticsConfiguration(); + if (properties.label != null) { + config.label = properties.label; + } + if (properties.textDirection != null) { + config.textDirection = properties.textDirection; + } + + newChild.updateWith( + config: config, + // As of now CustomPainter does not support multiple tree levels. + childrenInInversePaintOrder: const [], + ); + + newChild + ..rect = currentSemantics.rect + ..transform = currentSemantics.transform + ..tags = currentSemantics.tags; + + semanticsNodes.add(newChild); + } + + final List finalChildren = []; + finalChildren.addAll(semanticsNodes); + finalChildren.addAll(children); + + super.assembleSemanticsNode(node, config, finalChildren); + } + + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + return; + } + + List _getSemanticsBuilder(Size size) { + final List semanticsBuilder = + []; + double left, top; + Map leftAndTopValue; + double webUIPadding = 0; + double width = size.width; + double height = size.height; + int viewCount = 1; + final bool isHorizontalMultiView = _enableMultiView && + _navigationDirection == DateRangePickerNavigationDirection.horizontal; + final bool isVerticalMultiView = _enableMultiView && + _navigationDirection == DateRangePickerNavigationDirection.vertical; + + if (isHorizontalMultiView) { + webUIPadding = _multiViewSpacing; + viewCount = 2; + width = (width - webUIPadding) / viewCount; + } else if (isVerticalMultiView) { + webUIPadding = _multiViewSpacing; + viewCount = 2; + height = (height - webUIPadding) / viewCount; + } + + final double cellWidth = width / DateTime.daysPerWeek; + final double cellHeight = height / rowCount; + final int datesCount = _visibleDates.length ~/ viewCount; + for (int j = 0; j < viewCount; j++) { + final int currentViewIndex = + _isRtl ? DateRangePickerHelper.getRtlIndex(viewCount, j) : j; + left = _isRtl ? width - cellWidth : 0; + top = 0; + final dynamic middleDate = + _visibleDates[(j * datesCount) + (datesCount ~/ 2)]; + final double viewXStartPosition = isVerticalMultiView + ? 0 + : (currentViewIndex * width) + (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]; + if (!DateRangePickerHelper.isDateAsCurrentMonthDate(middleDate, + _rowCount, _showLeadingAndTailingDates, currentDate, _isHijri)) { + leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( + _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, + ), + )); + leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( + _isRtl, left, top, cellWidth, cellHeight, width); + left = leftAndTopValue['left']; + top = leftAndTopValue['top']; + continue; + } else if (!DateRangePickerHelper.isEnabledDate( + _minDate, _maxDate, _enablePastDates, currentDate, _isHijri)) { + 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); + 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, + ), + )); + leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( + _isRtl, left, top, cellWidth, cellHeight, width); + left = leftAndTopValue['left']; + top = leftAndTopValue['top']; + } + } + + return semanticsBuilder; + } + + /// Returns the accessibility text for the month cell. + String _getSemanticMonthLabel(dynamic date) { + if (_isHijri) { + return DateFormat('EEE').format(date).toString() + + ',' + + date.day.toString() + + '/' + + DateRangePickerHelper.getHijriMonthText(date, localizations, 'MMMM') + + '/' + + date.year.toString(); + } else { + return DateFormat('EEE, dd/MMMM/yyyy').format(date).toString(); + } + } +} + +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) + : 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); + + dynamic _selectedDate; + + dynamic get selectedDate => _selectedDate; + + set selectedDate(dynamic value) { + if (isSameDate(_selectedDate, value)) { + return; + } + + _selectedDate = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + _cellWidth = size.width / DateTime.daysPerWeek; + _cellHeight = size.height / rowCount; + if (enableMultiView) { + switch (_navigationDirection) { + case DateRangePickerNavigationDirection.horizontal: + { + _cellWidth = + (size.width - _multiViewSpacing) / (DateTime.daysPerWeek * 2); + } + break; + case DateRangePickerNavigationDirection.vertical: + { + _cellHeight = (size.height - _multiViewSpacing) / (2 * rowCount); + } + } + } + + _centerXPosition = _cellWidth / 2; + _centerYPosition = _cellHeight / 2; + _drawMonthCellsAndSelection(context, size, this, _cellWidth, _cellHeight); + } + + @override + 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); + } + break; + case DateRangePickerSelectionShape.rectangle: + { + _drawFillSelection( + canvas, x, y, _cellWidth, _cellHeight, _selectionPainter); + } + } + + return selectionTextStyle; + } + + @override + void drawCustomCellSelection(Canvas canvas, double x, double y, int index) { + _selectionPainter ??= Paint(); + _selectionPainter.color = selectionColor ?? datePickerTheme.selectionColor; + _selectionPainter.strokeWidth = 0.0; + _selectionPainter.style = PaintingStyle.fill; + _selectionPainter.isAntiAlias = true; + 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); + selectedIndex.add(index); + } + } + + return selectedIndex; + } + + @override + void updateSelection(PickerStateArgs details) { + if (isSameDate(details.selectedDate, selectedDate)) { + return; + } + + selectedDate = details.selectedDate; + selectionNotifier.value = !selectionNotifier.value; + } +} + +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) + : 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); + + List _selectedDates; + + List get selectedDates => _selectedDates; + + set selectedDates(List value) { + if (DateRangePickerHelper.isDateCollectionEquals(_selectedDates, value)) { + return; + } + + _selectedDates = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + @override + 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); + } + break; + case DateRangePickerSelectionShape.rectangle: + { + _drawFillSelection( + canvas, x, y, _cellWidth, _cellHeight, _selectionPainter); + } + } + + return selectionTextStyle; + } + + @override + void drawCustomCellSelection(Canvas canvas, double x, double y, int index) { + _selectionPainter ??= Paint(); + _selectionPainter.color = selectionColor ?? datePickerTheme.selectionColor; + _selectionPainter.strokeWidth = 0.0; + _selectionPainter.style = PaintingStyle.fill; + _selectionPainter.isAntiAlias = true; + canvas.drawRect(Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), + _selectionPainter); + } + + @override + List getSelectedIndexValues(int viewStartIndex, int viewEndIndex) { + final List selectedIndex = []; + if (selectedDates != null) { + for (int j = 0; j < selectedDates.length; j++) { + final dynamic date = selectedDates[j]; + if (!isDateWithInDateRange( + visibleDates[viewStartIndex], visibleDates[viewEndIndex], date)) { + continue; + } + + selectedIndex.add(_getSelectedIndex(date, visibleDates, + viewStartIndex: viewStartIndex)); + } + } + + return selectedIndex; + } + + @override + void paint(PaintingContext context, Offset offset) { + _cellWidth = size.width / DateTime.daysPerWeek; + _cellHeight = size.height / rowCount; + if (enableMultiView) { + switch (_navigationDirection) { + case DateRangePickerNavigationDirection.horizontal: + { + _cellWidth = + (size.width - _multiViewSpacing) / (DateTime.daysPerWeek * 2); + } + break; + case DateRangePickerNavigationDirection.vertical: + { + _cellHeight = (size.height - _multiViewSpacing) / (2 * rowCount); + } + } + } + + _centerXPosition = _cellWidth / 2; + _centerYPosition = _cellHeight / 2; + _drawMonthCellsAndSelection(context, size, this, _cellWidth, _cellHeight); + } + + @override + void updateSelection(PickerStateArgs details) { + if (DateRangePickerHelper.isDateCollectionEquals( + details.selectedDates, selectedDates)) { + return; + } + + selectedDates = DateRangePickerHelper.cloneList(details.selectedDates); + selectionNotifier.value = !selectionNotifier.value; + } +} + +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) + : 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); + + dynamic _selectedRange; + + dynamic get selectedRange => _selectedRange; + + set selectedRange(dynamic value) { + if (DateRangePickerHelper.isRangeEquals(_selectedRange, value)) { + return; + } + + _selectedRange = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + List _selectedIndex; + + @override + 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 heightDifference = _cellHeight / 2 - radius; + if (isSelectedDate) { + _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); + } else if (isEndRange) { + _selectionPainter.color = + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + _drawStartAndEndRange( + 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 selectionTextStyle; + } + + @override + void drawCustomCellSelection(Canvas canvas, double x, double y, int index) { + _selectionPainter ??= Paint(); + _selectionPainter.strokeWidth = 0.0; + _selectionPainter.style = PaintingStyle.fill; + _selectionPainter.isAntiAlias = true; + 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]; + if (isSelectedDate) { + _selectionPainter.color = + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + } else if (isStartRange) { + _selectionPainter.color = + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + } else if (isEndRange) { + _selectionPainter.color = + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor; + } else if (isBetweenRange) { + _selectionPainter.color = + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + } + + canvas.drawRect(Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), + _selectionPainter); + } + + List _getSelectedRangePosition(int index) { + /// isSelectedDate value used to notify the year cell as selected and + /// the range hold only start date value on range and multi range selection. + bool isSelectedDate = false; + + /// isStartRange value used to notify the year cell as selected and + /// the year cell as start date cell of the picker date range. + /// its selection mode as range or multi range. + bool isStartRange = false; + + /// isEndRange value used to notify the year cell as selected and + /// the year cell as end date cell of the picker date range. + /// its selection mode as range or multi range. + bool isEndRange = false; + + /// isBetweenRange value used to notify the year cell as selected and + /// the year cell as in between the start and end date cell of the + /// picker date range. its selection mode as range or multi range. + bool isBetweenRange = false; + if (_selectedIndex.length == 1) { + isSelectedDate = true; + } else if (_selectedIndex[0] == index) { + if (isRtl) { + isEndRange = true; + } else { + isStartRange = true; + } + } else if (_selectedIndex[_selectedIndex.length - 1] == index) { + if (isRtl) { + isStartRange = true; + } else { + isEndRange = true; + } + } else { + isBetweenRange = true; + } + + return [isSelectedDate, isStartRange, isEndRange, isBetweenRange]; + } + + @override + List getSelectedIndexValues(int viewStartIndex, int viewEndIndex) { + _selectedIndex = []; + if (selectedRange != null) { + final dynamic startDate = selectedRange.startDate; + final dynamic endDate = selectedRange.endDate ?? selectedRange.startDate; + _selectedIndex = _getSelectedRangeIndex(startDate, endDate, visibleDates, + monthStartIndex: viewStartIndex, monthEndIndex: viewEndIndex); + } + + return _selectedIndex; + } + + @override + void paint(PaintingContext context, Offset offset) { + _cellWidth = size.width / DateTime.daysPerWeek; + _cellHeight = size.height / rowCount; + if (enableMultiView) { + switch (_navigationDirection) { + case DateRangePickerNavigationDirection.horizontal: + { + _cellWidth = + (size.width - _multiViewSpacing) / (DateTime.daysPerWeek * 2); + } + break; + case DateRangePickerNavigationDirection.vertical: + { + _cellHeight = (size.height - _multiViewSpacing) / (2 * rowCount); + } + } + } + + _centerXPosition = _cellWidth / 2; + _centerYPosition = _cellHeight / 2; + _drawMonthCellsAndSelection(context, size, this, _cellWidth, _cellHeight); + } + + @override + void updateSelection(PickerStateArgs details) { + if (DateRangePickerHelper.isRangeEquals( + details.selectedRange, selectedRange)) { + return; + } + + selectedRange = details.selectedRange; + selectionNotifier.value = !selectionNotifier.value; + } +} + +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) + : 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); + + List _selectedRanges; + + List get selectedRanges => _selectedRanges; + + set selectedRanges(List value) { + if (DateRangePickerHelper.isDateRangesEquals(_selectedRanges, value)) { + return; + } + + _selectedRanges = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + List> _selectedRangesIndex; + + @override + 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 heightDifference = _cellHeight / 2 - radius; + if (isSelectedDate) { + _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); + } else if (isEndRange) { + _selectionPainter.color = + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + _drawStartAndEndRange( + 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 selectionTextStyle; + } + + @override + void drawCustomCellSelection(Canvas canvas, double x, double y, int index) { + _selectionPainter ??= Paint(); + _selectionPainter.strokeWidth = 0.0; + _selectionPainter.style = PaintingStyle.fill; + _selectionPainter.isAntiAlias = true; + 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]; + if (isSelectedDate) { + _selectionPainter.color = + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + } else if (isStartRange) { + _selectionPainter.color = + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + } else if (isEndRange) { + _selectionPainter.color = + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor; + } else if (isBetweenRange) { + _selectionPainter.color = + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + } + + canvas.drawRect(Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), + _selectionPainter); + } + + List _getSelectedRangePosition(int index) { + /// isSelectedDate value used to notify the year cell as selected and + /// the range hold only start date value on range and multi range selection. + bool isSelectedDate = false; + + /// isStartRange value used to notify the year cell as selected and + /// the year cell as start date cell of the picker date range. + /// its selection mode as range or multi range. + bool isStartRange = false; + + /// isEndRange value used to notify the year cell as selected and + /// the year cell as end date cell of the picker date range. + /// its selection mode as range or multi range. + bool isEndRange = false; + + /// isBetweenRange value used to notify the year cell as selected and + /// the year cell as in between the start and end date cell of the + /// picker date range. its selection mode as range or multi range. + bool isBetweenRange = false; + for (int j = 0; j < _selectedRangesIndex.length; j++) { + final List rangeIndex = _selectedRangesIndex[j]; + if (!rangeIndex.contains(index)) { + continue; + } + + if (rangeIndex.length == 1) { + isSelectedDate = true; + } else if (rangeIndex[0] == index) { + if (isRtl) { + isEndRange = true; + } else { + isStartRange = true; + } + } else if (rangeIndex[rangeIndex.length - 1] == index) { + if (isRtl) { + isStartRange = true; + } else { + isEndRange = true; + } + } else { + isBetweenRange = true; + } + + break; + } + + return [isSelectedDate, isStartRange, isEndRange, isBetweenRange]; + } + + @override + List getSelectedIndexValues(int viewStartIndex, int viewEndIndex) { + final List selectedIndex = []; + _selectedRangesIndex = >[]; + if (selectedRanges != null) { + for (int j = 0; j < selectedRanges.length; j++) { + final dynamic range = selectedRanges[j]; + final dynamic startDate = range.startDate; + final dynamic endDate = range.endDate ?? range.startDate; + final List rangeIndex = _getSelectedRangeIndex( + startDate, endDate, visibleDates, + monthStartIndex: viewStartIndex, monthEndIndex: viewEndIndex); + for (int i = 0; i < rangeIndex.length; i++) { + selectedIndex.add(rangeIndex[i]); + } + + _selectedRangesIndex.add(rangeIndex); + } + } + + return selectedIndex; + } + + @override + void paint(PaintingContext context, Offset offset) { + _cellWidth = size.width / DateTime.daysPerWeek; + _cellHeight = size.height / rowCount; + if (enableMultiView) { + switch (_navigationDirection) { + case DateRangePickerNavigationDirection.horizontal: + { + _cellWidth = + (size.width - _multiViewSpacing) / (DateTime.daysPerWeek * 2); + } + break; + case DateRangePickerNavigationDirection.vertical: + { + _cellHeight = (size.height - _multiViewSpacing) / (2 * rowCount); + } + } + } + + _centerXPosition = _cellWidth / 2; + _centerYPosition = _cellHeight / 2; + _drawMonthCellsAndSelection(context, size, this, _cellWidth, _cellHeight); + } + + @override + void updateSelection(PickerStateArgs details) { + if (DateRangePickerHelper.isDateRangesEquals( + details.selectedRanges, selectedRanges)) { + return; + } + + selectedRanges = DateRangePickerHelper.cloneList(details.selectedRanges); + selectionNotifier.value = !selectionNotifier.value; + } +} + +void _drawSelectedDate( + 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.datePickerTheme.startRangeSelectionColor; + switch (view.selectionShape) { + case DateRangePickerSelectionShape.circle: + { + _drawCircleSelection(canvas, x + centerXPosition, y + centerYPosition, + radius, view._selectionPainter); + } + break; + case DateRangePickerSelectionShape.rectangle: + { + _drawFillSelection( + 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) { + switch (view.selectionShape) { + case DateRangePickerSelectionShape.circle: + { + Rect rect; + if (isStartRange) { + rect = Rect.fromLTRB(x + centerXPosition, y + heightDifference, + x + cellWidth, y + cellHeight - heightDifference); + } else { + rect = Rect.fromLTRB(x, y + heightDifference, x + centerXPosition, + y + cellHeight - heightDifference); + } + + _drawStartEndRangeCircleSelection(canvas, x + centerXPosition, + y + centerYPosition, radius, rect, view._selectionPainter, color); + } + break; + case DateRangePickerSelectionShape.rectangle: + { + view._selectionPainter.isAntiAlias = true; + view._selectionPainter.color = color; + if (isStartRange) { + _drawStartRangeFillSelection( + canvas, x, y, cellWidth, cellHeight, view._selectionPainter); + } else { + _drawEndRangeFillSelection( + 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) { + switch (view.selectionShape) { + case DateRangePickerSelectionShape.rectangle: + heightDifference = 1; + break; + case DateRangePickerSelectionShape.circle: + break; + } + + view._selectionPainter.color = + view.rangeSelectionColor ?? view.datePickerTheme.rangeSelectionColor; + _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; + + if (selectionRadius == null) { + return radius; + } + + return radius > selectionRadius ? selectionRadius : radius; +} + +List _getSelectedRangeIndex( + dynamic startDate, dynamic endDate, List visibleDates, + {int monthStartIndex = -1, int monthEndIndex = -1}) { + int startIndex = -1; + int endIndex = -1; + final List selectedIndex = []; + if (startDate != null && startDate.isAfter(endDate)) { + final dynamic temp = startDate; + startDate = endDate; + endDate = temp; + } + + final dynamic viewStartDate = + monthStartIndex != -1 ? visibleDates[monthStartIndex] : visibleDates[0]; + final dynamic viewEndDate = monthEndIndex != -1 + ? visibleDates[monthEndIndex] + : visibleDates[visibleDates.length - 1]; + if (startDate != null) { + if (viewStartDate.isAfter(startDate) && viewStartDate.isBefore(endDate)) { + startIndex = -1; + } else { + startIndex = _getSelectedIndex(startDate, visibleDates, + viewStartIndex: monthStartIndex); + } + } + + if (endDate != null) { + if (viewEndDate.isAfter(startDate) && viewEndDate.isBefore(endDate)) { + endIndex = visibleDates.length; + } else { + endIndex = _getSelectedIndex(endDate, visibleDates, + viewStartIndex: monthStartIndex); + } + } + + //// If some range end date as null then it end index is start index. + if (startIndex != -1 && endIndex == -1) { + endIndex = startIndex; + } + + if (startIndex > endIndex) { + final int temp = startIndex; + startIndex = endIndex; + endIndex = temp; + } + + for (int i = startIndex; i <= endIndex; i++) { + selectedIndex.add(i); + } + + return selectedIndex; +} + +int _getSelectedIndex(dynamic date, List visibleDates, + {int viewStartIndex = 0}) { + if (viewStartIndex == -1) { + viewStartIndex = 0; + } + + for (int i = viewStartIndex; i < visibleDates.length; i++) { + if (isSameDate(visibleDates[i], date)) { + return i; + } + } + + return -1; +} + +void _drawCircleSelection( + 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) { + 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); +} + +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); +} + +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); +} + +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) { + canvas.drawRect(Rect.fromLTRB(left, top, right, bottom), selectionPainter); +} + +void _drawMonthCellsAndSelection(PaintingContext context, Size size, + _IMonthView monthView, double cellWidth, double cellHeight) { + final Canvas canvas = context.canvas; + double xPosition = 0, yPosition; + double webUIPadding = 0; + double width = size.width; + double height = size.height; + int viewCount = 1; + final bool isHorizontalMultiView = monthView.enableMultiView && + monthView.navigationDirection == + DateRangePickerNavigationDirection.horizontal; + final bool isVerticalMultiView = monthView.enableMultiView && + monthView.navigationDirection == + DateRangePickerNavigationDirection.vertical; + + if (isHorizontalMultiView) { + webUIPadding = monthView.multiViewSpacing; + viewCount = 2; + width = (width - webUIPadding) / viewCount; + } else if (isVerticalMultiView) { + webUIPadding = monthView.multiViewSpacing; + viewCount = 2; + height = (height - webUIPadding) / viewCount; + } + + monthView._textPainter ??= TextPainter( + textDirection: TextDirection.ltr, + textScaleFactor: monthView.textScaleFactor, + textWidthBasis: TextWidthBasis.longestLine); + TextStyle textStyle = monthView.cellStyle.textStyle; + final int datesCount = monthView.visibleDates.length ~/ viewCount; + final bool isNeedWidgetPaint = monthView.childCount != 0; + final bool hideLeadingAndTrailingDates = + (monthView.rowCount == 6 && !monthView.showLeadingAndTailingDates) || + monthView.isHijri; + if (isNeedWidgetPaint) { + RenderBox child = monthView.firstChild; + 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; + + final int viewStartIndex = j * datesCount; + final int viewEndIndex = ((j + 1) * datesCount) - 1; + final List selectedIndex = + monthView.getSelectedIndexValues(viewStartIndex, viewEndIndex); + final double viewStartPosition = isVerticalMultiView + ? 0 + : (currentViewIndex * width) + (currentViewIndex * webUIPadding); + final double viewEndPosition = viewStartPosition + width; + xPosition = viewStartPosition; + yPosition = yPosition = isHorizontalMultiView + ? 0 + : (currentViewIndex * height) + (currentViewIndex * webUIPadding); + 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) + + (rowIndex * DateTime.daysPerWeek); + } + + currentIndex = (j * datesCount) + currentIndex; + final dynamic date = monthView.visibleDates[currentIndex]; + final int currentDateMonth = date.month; + + /// Check the x position reaches view end position then draw the + /// date on next cell. + /// Padding 1 value used to avoid decimal value difference. + /// eg., if view end position as 243 and x position as 242.499 then + /// round method in decimal return 242 rather than 243, so it does + /// not move the next line for draw date value. + if (xPosition + 1 >= viewEndPosition) { + xPosition = viewStartPosition; + yPosition += cellHeight; + } + + if (hideLeadingAndTrailingDates && currentDateMonth != currentMonth) { + xPosition += cellWidth; + continue; + } + + final bool isEnableDate = DateRangePickerHelper.isEnabledDate( + monthView.minDate, + monthView.maxDate, + monthView.enablePastDates, + date, + monthView.isHijri); + final bool isBlackedDate = + DateRangePickerHelper.isDateWithInVisibleDates( + monthView.visibleDates, monthView.blackoutDates, date); + final bool isSelectedDate = selectedIndex.contains(currentIndex); + + if (isSelectedDate && + !isBlackedDate && + isEnableDate && + (!monthView.enableMultiView || + (monthView.rowCount != 6 || + (currentMonth == currentDateMonth)))) { + monthView.drawCustomCellSelection( + canvas, xPosition, yPosition, currentIndex); + } + + child.paint(context, Offset(xPosition, yPosition)); + child = monthView.childAfter(child); + if (monthView.mouseHoverPosition != null && + monthView.mouseHoverPosition.value != null) { + if (isSelectedDate || isBlackedDate || !isEnableDate) { + xPosition += cellWidth; + continue; + } + + if (xPosition <= monthView.mouseHoverPosition.value.dx && + xPosition + cellWidth >= monthView.mouseHoverPosition.value.dx && + yPosition <= monthView.mouseHoverPosition.value.dy && + yPosition + cellHeight >= monthView.mouseHoverPosition.value.dy) { + monthView._selectionPainter ??= Paint(); + 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); + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromLTWH(xPosition, yPosition, cellWidth, cellHeight), + Radius.circular(2)), + monthView._selectionPainter); + } + } + + xPosition += cellWidth; + } + } + + return; + } + + final dynamic today = DateRangePickerHelper.getToday(monthView.isHijri); + 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 int nextMonth = getNextMonthDate(currentMonthDate).month; + final int previousMonth = getPreviousMonthDate(currentMonthDate).month; + bool isCurrentDate; + final TextStyle selectionTextStyle = monthView.selectionTextStyle ?? + monthView.datePickerTheme.selectionTextStyle; + final TextStyle selectedRangeTextStyle = monthView.rangeTextStyle ?? + monthView.datePickerTheme.rangeSelectionTextStyle; + + Decoration dateDecoration; + const double padding = 1; + + final int viewStartIndex = j * datesCount; + final int viewEndIndex = ((j + 1) * datesCount) - 1; + final List selectedIndex = + monthView.getSelectedIndexValues(viewStartIndex, viewEndIndex); + final double viewStartPosition = isVerticalMultiView + ? 0 + : (currentViewIndex * width) + (currentViewIndex * webUIPadding); + final double viewEndPosition = viewStartPosition + width; + xPosition = viewStartPosition; + yPosition = isHorizontalMultiView + ? 0 + : (currentViewIndex * height) + (currentViewIndex * webUIPadding); + 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) + + (rowIndex * DateTime.daysPerWeek); + } + + isCurrentDate = false; + currentIndex = (j * datesCount) + currentIndex; + final dynamic date = monthView.visibleDates[currentIndex]; + final int currentDateMonth = date.month; + + /// Check the x position reaches view end position then draw the + /// date on next cell. + /// Padding 1 value used to avoid decimal value difference. + /// eg., if view end position as 243 and x position as 242.499 then + /// round method in decimal return 242 rather than 243, so it does + /// not move the next line for draw date value. + if (xPosition + 1 >= viewEndPosition) { + xPosition = viewStartPosition; + yPosition += cellHeight; + } + + bool isNextMonth = false; + bool isPreviousMonth = false; + if (monthView.rowCount == 6 || monthView.isHijri) { + if (currentDateMonth == nextMonth) { + if (!monthView.showLeadingAndTailingDates || monthView.isHijri) { + xPosition += cellWidth; + continue; + } + isNextMonth = true; + } else if (currentDateMonth == previousMonth) { + if (!monthView.showLeadingAndTailingDates || monthView.isHijri) { + xPosition += cellWidth; + continue; + } + isPreviousMonth = true; + } + } + + isCurrentDate = isSameDate(date, today); + final bool isEnableDate = DateRangePickerHelper.isEnabledDate( + 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); + final bool isSpecialDate = DateRangePickerHelper.isDateWithInVisibleDates( + monthView.visibleDates, monthView.specialDates, date); + + textStyle = _updateTextStyle(monthView, isNextMonth, isPreviousMonth, + isCurrentDate, isEnableDate, isBlackedDate, isWeekEnd, isSpecialDate); + dateDecoration = _updateDecoration( + isNextMonth, + isPreviousMonth, + monthView, + isEnableDate, + isCurrentDate, + isBlackedDate, + date, + isWeekEnd, + isSpecialDate); + + final bool isSelectedDate = selectedIndex.contains(currentIndex); + if (isSelectedDate && + !isBlackedDate && + isEnableDate && + (!monthView.enableMultiView || + (monthView.rowCount != 6 || + (currentMonthDate.month == currentDateMonth)))) { + textStyle = _drawCellAndSelection( + canvas, + xPosition, + yPosition, + selectionTextStyle, + selectedRangeTextStyle, + monthView, + currentIndex); + } else if (dateDecoration != null) { + _drawDecoration(canvas, xPosition, yPosition, padding, cellWidth, + cellHeight, dateDecoration, monthView); + } else if (isCurrentDate) { + _drawCurrentDate(canvas, monthView, xPosition, yPosition, padding, + cellWidth, cellHeight); + } + + final TextSpan dateText = TextSpan( + text: date.day.toString(), + style: textStyle, + ); + + monthView._textPainter.text = dateText; + monthView._textPainter.layout(minWidth: cellWidth, maxWidth: cellWidth); + monthView._textPainter.paint( + canvas, + Offset(xPosition + (cellWidth / 2 - monthView._textPainter.width / 2), + yPosition + ((cellHeight - monthView._textPainter.height) / 2))); + if (monthView.mouseHoverPosition != null && + monthView.mouseHoverPosition.value != null) { + if (isSelectedDate || isBlackedDate || !isEnableDate) { + xPosition += cellWidth; + continue; + } + + _addHoveringEffect( + canvas, monthView, xPosition, yPosition, cellWidth, cellHeight); + } + + xPosition += cellWidth; + } + } +} + +void _addHoveringEffect(Canvas canvas, _IMonthView monthView, double xPosition, + double yPosition, double cellWidth, double cellHeight) { + if (xPosition <= monthView.mouseHoverPosition.value.dx && + xPosition + cellWidth >= monthView.mouseHoverPosition.value.dx && + yPosition <= monthView.mouseHoverPosition.value.dy && + yPosition + cellHeight >= monthView.mouseHoverPosition.value.dy) { + monthView._selectionPainter = monthView._selectionPainter ?? Paint(); + 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); + switch (monthView.selectionShape) { + case DateRangePickerSelectionShape.circle: + { + final double centerXPosition = cellWidth / 2; + final double centerYPosition = cellHeight / 2; + final double radius = _getCellRadius( + monthView.selectionRadius, centerXPosition, centerYPosition); + canvas.drawCircle( + 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); + } + } + } +} + +TextStyle _drawCellAndSelection( + Canvas canvas, + double xPosition, + double yPosition, + TextStyle selectionTextStyle, + TextStyle selectedRangeTextStyle, + _IMonthView monthView, + int currentIndex) { + monthView._selectionPainter = monthView._selectionPainter ?? Paint(); + 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); +} + +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); + boxPainter.paint( + 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 = monthView._selectionPainter ?? Paint(); + monthView._selectionPainter.color = monthView.todayHighlightColor ?? + monthView.datePickerTheme.todayHighlightColor; + monthView._selectionPainter.isAntiAlias = true; + monthView._selectionPainter.strokeWidth = 1.0; + monthView._selectionPainter.style = PaintingStyle.stroke; + + switch (monthView.selectionShape) { + case DateRangePickerSelectionShape.circle: + { + final double centerXPosition = cellWidth / 2; + final double centerYPosition = cellHeight / 2; + final double radius = _getCellRadius( + monthView.selectionRadius, centerXPosition, centerYPosition); + canvas.drawCircle( + 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); + } + } +} + +TextStyle _updateTextStyle( + _IMonthView monthView, + bool isNextMonth, + bool isPreviousMonth, + bool isCurrentDate, + bool isEnableDate, + bool isBlackedDate, + bool isWeekEnd, + bool isSpecialDate) { + final TextStyle currentDatesTextStyle = monthView.cellStyle.textStyle ?? + monthView.datePickerTheme.activeDatesTextStyle; + if (isBlackedDate) { + return monthView.cellStyle.blackoutDateTextStyle ?? + (monthView.datePickerTheme.blackoutDatesTextStyle ?? + currentDatesTextStyle.copyWith( + decoration: TextDecoration.lineThrough)); + } + + if (isSpecialDate) { + return monthView.cellStyle.specialDatesTextStyle ?? + monthView.datePickerTheme.specialDatesTextStyle; + } + + if (!isEnableDate) { + return monthView.cellStyle.disabledDatesTextStyle ?? + monthView.datePickerTheme.disabledDatesTextStyle; + } + + if (isCurrentDate) { + return monthView.cellStyle.todayTextStyle ?? + monthView.datePickerTheme.todayTextStyle; + } + + if (isWeekEnd && monthView.cellStyle.weekendTextStyle != null) { + return monthView.cellStyle.weekendTextStyle; + } else if (isWeekEnd && + monthView.datePickerTheme != null && + monthView.datePickerTheme.weekendDatesTextStyle != null) { + return monthView.datePickerTheme.weekendDatesTextStyle; + } + + if (isNextMonth && !monthView.isHijri) { + return monthView.cellStyle.leadingDatesTextStyle ?? + monthView.datePickerTheme.leadingDatesTextStyle; + } else if (isPreviousMonth && !monthView.isHijri) { + return monthView.cellStyle.trailingDatesTextStyle ?? + monthView.datePickerTheme.trailingDatesTextStyle; + } + + return currentDatesTextStyle; +} + +Decoration _updateDecoration( + bool isNextMonth, + bool isPreviousMonth, + _IMonthView monthView, + isEnableDate, + isCurrentDate, + isBlackedDate, + dynamic date, + bool isWeekEnd, + bool isSpecialDate) { + final Decoration dateDecoration = monthView.cellStyle.cellDecoration; + + if (isBlackedDate) { + return monthView.cellStyle.blackoutDatesDecoration; + } + + if (isSpecialDate) { + return monthView.cellStyle.specialDatesDecoration; + } + + if (!isEnableDate) { + return monthView.cellStyle.disabledDatesDecoration; + } + + if (isCurrentDate) { + return monthView.cellStyle.todayCellDecoration ?? dateDecoration; + } + + if (isWeekEnd && monthView.cellStyle.weekendDatesDecoration != null) { + return monthView.cellStyle.weekendDatesDecoration; + } + + if (isNextMonth && !monthView.isHijri) { + return monthView.cellStyle.leadingDatesDecoration; + } else if (isPreviousMonth && !monthView.isHijri) { + return monthView.cellStyle.trailingDatesDecoration; + } + + return dateDecoration; +} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/helper.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/helper.dart deleted file mode 100644 index ca26dc28c..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/helper.dart +++ /dev/null @@ -1,732 +0,0 @@ -part of datepicker; - -int _getRtlIndex(int count, int index) { - return count - index - 1; -} - -List _cloneList(List value) { - if (value == null || value.isEmpty) { - return null; - } - - return value.sublist(0); -} - -void _drawSelectedDate( - Canvas canvas, - double radius, - double centerXPosition, - double cellWidth, - double cellHeight, - double x, - double y, - _IMonthViewPainter view, - double centerYPosition) { - view.selectionPainter.isAntiAlias = true; - view.selectionPainter.color = view.startRangeSelectionColor ?? - view.datePickerTheme.startRangeSelectionColor; - switch (view.selectionShape) { - case DateRangePickerSelectionShape.circle: - { - _drawCircleSelection(canvas, x + centerXPosition, y + centerYPosition, - radius, view.selectionPainter); - } - break; - case DateRangePickerSelectionShape.rectangle: - { - _drawFillSelection( - canvas, x, y, cellWidth, cellHeight, view.selectionPainter); - } - } -} - -void _drawStartAndEndRange( - Canvas canvas, - _IMonthViewPainter 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); - } else { - rect = Rect.fromLTRB(x, y + heightDifference, x + centerXPosition, - y + cellHeight - heightDifference); - } - - _drawStartEndRangeCircleSelection(canvas, x + centerXPosition, - y + centerYPosition, radius, rect, view.selectionPainter, color); - } - break; - case DateRangePickerSelectionShape.rectangle: - { - if (isStartRange) { - _drawStartRangeFillSelection( - canvas, x, y, cellWidth, cellHeight, view.selectionPainter); - } else { - _drawEndRangeFillSelection( - canvas, x, y, cellWidth, cellHeight, view.selectionPainter); - } - } - } -} - -TextStyle _drawBetweenSelection( - Canvas canvas, - _IMonthViewPainter view, - double cellWidth, - double cellHeight, - double radius, - double x, - double y, - double heightDifference, - TextStyle selectionRangeTextStyle) { - switch (view.selectionShape) { - case DateRangePickerSelectionShape.rectangle: - heightDifference = 1; - break; - case DateRangePickerSelectionShape.circle: - break; - } - - view.selectionPainter.color = - view.rangeSelectionColor ?? view.datePickerTheme.rangeSelectionColor; - _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 - _IMonthViewPainter._selectionPadding - : maxXRadius - _IMonthViewPainter._selectionPadding; - - if (selectionRadius == null) { - return radius; - } - - return radius > selectionRadius ? selectionRadius : radius; -} - -List _getSelectedRangeIndex( - DateTime startDate, DateTime endDate, List visibleDates, - {int monthStartIndex = -1, int monthEndIndex = -1}) { - int startIndex = -1; - int endIndex = -1; - final List selectedIndex = []; - if (startDate != null && startDate.isAfter(endDate)) { - final DateTime temp = startDate; - startDate = endDate; - endDate = temp; - } - - final DateTime viewStartDate = - monthStartIndex != -1 ? visibleDates[monthStartIndex] : visibleDates[0]; - final DateTime viewEndDate = monthEndIndex != -1 - ? visibleDates[monthEndIndex] - : visibleDates[visibleDates.length - 1]; - if (startDate != null) { - if (viewStartDate.isAfter(startDate) && viewStartDate.isBefore(endDate)) { - startIndex = -1; - } else { - startIndex = _getSelectedIndex(startDate, visibleDates, - viewStartIndex: monthStartIndex); - } - } - - if (endDate != null) { - if (viewEndDate.isAfter(startDate) && viewEndDate.isBefore(endDate)) { - endIndex = visibleDates.length; - } else { - endIndex = _getSelectedIndex(endDate, visibleDates, - viewStartIndex: monthStartIndex); - } - } - - //// If some range end date as null then it end index is start index. - if (startIndex != -1 && endIndex == -1) { - endIndex = startIndex; - } - - if (startIndex > endIndex) { - final int temp = startIndex; - startIndex = endIndex; - endIndex = temp; - } - - for (int i = startIndex; i <= endIndex; i++) { - selectedIndex.add(i); - } - - return selectedIndex; -} - -int _getSelectedIndex(DateTime date, List visibleDates, - {int viewStartIndex = 0}) { - if (viewStartIndex == -1) { - viewStartIndex = 0; - } - - for (int i = viewStartIndex; i < visibleDates.length; i++) { - if (isSameDate(visibleDates[i], date)) { - return i; - } - } - - return -1; -} - -void _drawCircleSelection( - 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) { - 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); -} - -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); -} - -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); -} - -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) { - canvas.drawRect(Rect.fromLTRB(left, top, right, bottom), selectionPainter); -} - -void _drawMonthCellsAndSelection(Canvas canvas, Size size, - _IMonthViewPainter monthViewPainter, double cellWidth, double cellHeight) { - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - double xPosition = 0, yPosition; - final double webUIPadding = monthViewPainter.multiViewSpacing; - double width = size.width; - if (monthViewPainter.enableMultiView) { - width = (width - webUIPadding) / 2; - } - - monthViewPainter.textPainter ??= TextPainter( - textDirection: TextDirection.ltr, - textScaleFactor: monthViewPainter.textScaleFactor, - textWidthBasis: TextWidthBasis.longestLine); - TextStyle textStyle = monthViewPainter.cellStyle.textStyle; - final int datesCount = monthViewPainter.enableMultiView - ? (monthViewPainter.visibleDates.length ~/ 2) - : monthViewPainter.visibleDates.length; - for (int j = 0; j < (monthViewPainter.enableMultiView ? 2 : 1); j++) { - final int currentViewIndex = monthViewPainter.isRtl - ? _getRtlIndex(monthViewPainter.enableMultiView ? 2 : 1, j) - : j; - final DateTime _currentDate = monthViewPainter - .visibleDates[((j * datesCount) + (datesCount / 2)).truncate()]; - final int nextMonth = getNextMonthDate(_currentDate).month; - final int previousMonth = getPreviousMonthDate(_currentDate).month; - final DateTime today = DateTime.now(); - bool isCurrentDate; - final TextStyle selectionTextStyle = monthViewPainter.selectionTextStyle ?? - monthViewPainter.datePickerTheme.selectionTextStyle; - final TextStyle selectedRangeTextStyle = monthViewPainter.rangeTextStyle ?? - monthViewPainter.datePickerTheme.rangeSelectionTextStyle; - - Decoration dateDecoration; - const double padding = 1; - - final int viewStartIndex = j * datesCount; - final int viewEndIndex = ((j + 1) * datesCount) - 1; - final List selectedIndex = - monthViewPainter._getSelectedIndexValues(viewStartIndex, viewEndIndex); - final double viewEndPosition = - ((currentViewIndex + 1) * width) + (currentViewIndex * webUIPadding); - final double viewStartPosition = - (currentViewIndex * width) + (currentViewIndex * webUIPadding); - xPosition = viewStartPosition; - yPosition = 0; - for (int i = 0; i < datesCount; i++) { - int currentIndex = i; - if (monthViewPainter.isRtl) { - final int rowIndex = i ~/ _kNumberOfDaysInWeek; - currentIndex = - _getRtlIndex(_kNumberOfDaysInWeek, i % _kNumberOfDaysInWeek) + - (rowIndex * _kNumberOfDaysInWeek); - } - - isCurrentDate = false; - currentIndex = (j * datesCount) + currentIndex; - final DateTime date = monthViewPainter.visibleDates[currentIndex]; - isCurrentDate = isSameDate(date, today); - final bool isEnableDate = _isEnabledDate(monthViewPainter.minDate, - monthViewPainter.maxDate, monthViewPainter.enablePastDates, date); - final bool isBlackedDate = _isDateWithInVisibleDates( - monthViewPainter.visibleDates, monthViewPainter.blackoutDates, date); - final bool isWeekEnd = _isWeekend(monthViewPainter.weekendDays, date); - final bool isSpecialDate = _isDateWithInVisibleDates( - monthViewPainter.visibleDates, monthViewPainter.specialDates, date); - - /// Check the x position reaches view end position then draw the - /// date on next cell. - /// Padding 1 value used to avoid decimal value difference. - /// eg., if view end position as 243 and x position as 242.499 then - /// round method in decimal return 242 rather than 243, so it does - /// not move the next line for draw date value. - if (xPosition + 1 >= viewEndPosition) { - xPosition = viewStartPosition; - yPosition += cellHeight; - } - - bool isNextMonth = false; - bool isPreviousMonth = false; - if (monthViewPainter.rowCount == 6) { - if (date.month == nextMonth) { - if (!monthViewPainter.showLeadingAndTailingDates) { - xPosition += cellWidth; - continue; - } - isNextMonth = true; - } else if (date.month == previousMonth) { - if (!monthViewPainter.showLeadingAndTailingDates) { - xPosition += cellWidth; - continue; - } - isPreviousMonth = false; - } - } - - textStyle = _updateTextStyle( - monthViewPainter, - isNextMonth, - isPreviousMonth, - isCurrentDate, - isEnableDate, - isBlackedDate, - isWeekEnd, - isSpecialDate); - dateDecoration = _updateDecoration( - isNextMonth, - isPreviousMonth, - monthViewPainter, - isEnableDate, - isCurrentDate, - isBlackedDate, - date, - isWeekEnd, - isSpecialDate); - - if (selectedIndex.contains(currentIndex) && - !isBlackedDate && - isEnableDate && - (!monthViewPainter.enableMultiView || - (monthViewPainter.rowCount != 6 || - (_currentDate.month == date.month)))) { - textStyle = _drawCellAndSelection( - canvas, - xPosition, - yPosition, - selectionTextStyle, - selectedRangeTextStyle, - monthViewPainter, - currentIndex); - } else if (dateDecoration != null) { - _drawDecoration(canvas, xPosition, yPosition, padding, cellWidth, - cellHeight, dateDecoration); - } else if (isCurrentDate) { - _drawCurrentDate(canvas, monthViewPainter, xPosition, yPosition, - padding, cellWidth, cellHeight); - } - - final TextSpan dateText = TextSpan( - text: date.day.toString(), - style: textStyle, - ); - - monthViewPainter.textPainter.text = dateText; - monthViewPainter.textPainter.layout(minWidth: 0, maxWidth: cellWidth); - monthViewPainter.textPainter.paint( - canvas, - Offset( - xPosition + - (cellWidth / 2 - monthViewPainter.textPainter.width / 2), - yPosition + - ((cellHeight - monthViewPainter.textPainter.height) / 2))); - if (monthViewPainter.mouseHoverPosition != null) { - if (selectedIndex.contains(currentIndex) || - isBlackedDate || - !isEnableDate) { - xPosition += cellWidth; - continue; - } - - _addHoveringEffect(canvas, monthViewPainter, xPosition, yPosition, - cellWidth, cellHeight); - } - - xPosition += cellWidth; - } - } -} - -void _addHoveringEffect(Canvas canvas, _IMonthViewPainter monthViewPainter, - double xPosition, double yPosition, double cellWidth, double cellHeight) { - if (xPosition <= monthViewPainter.mouseHoverPosition.dx && - xPosition + cellWidth >= monthViewPainter.mouseHoverPosition.dx && - yPosition - 5 <= monthViewPainter.mouseHoverPosition.dy && - (yPosition + cellHeight) - 5 >= monthViewPainter.mouseHoverPosition.dy) { - monthViewPainter.selectionPainter = - monthViewPainter.selectionPainter ?? Paint(); - monthViewPainter.selectionPainter.style = PaintingStyle.fill; - monthViewPainter.selectionPainter.strokeWidth = 2; - monthViewPainter.selectionPainter.color = - monthViewPainter.selectionColor != null - ? monthViewPainter.selectionColor.withOpacity(0.4) - : monthViewPainter.datePickerTheme.selectionColor.withOpacity(0.4); - switch (monthViewPainter.selectionShape) { - case DateRangePickerSelectionShape.circle: - { - final double centerXPosition = cellWidth / 2; - final double centerYPosition = cellHeight / 2; - final double radius = _getCellRadius(monthViewPainter.selectionRadius, - centerXPosition, centerYPosition); - canvas.drawCircle( - Offset(xPosition + centerXPosition, yPosition + centerYPosition), - radius, - monthViewPainter.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)), - monthViewPainter.selectionPainter); - } - } - } -} - -TextStyle _drawCellAndSelection( - Canvas canvas, - double xPosition, - double yPosition, - TextStyle selectionTextStyle, - TextStyle selectedRangeTextStyle, - _IMonthViewPainter monthViewPainter, - int currentIndex) { - monthViewPainter.selectionPainter = - monthViewPainter.selectionPainter ?? Paint(); - monthViewPainter.selectionPainter.color = monthViewPainter.selectionColor ?? - monthViewPainter.datePickerTheme.selectionColor; - //// Unwanted space shown at end of the rectangle while enable anti aliasing property. - monthViewPainter.selectionPainter.isAntiAlias = false; - monthViewPainter.selectionPainter.strokeWidth = 0.0; - monthViewPainter.selectionPainter.style = PaintingStyle.fill; - return monthViewPainter._drawSelection(canvas, xPosition, yPosition, - currentIndex, selectionTextStyle, selectedRangeTextStyle); -} - -void _drawDecoration( - Canvas canvas, - double xPosition, - double yPosition, - double padding, - double cellWidth, - double cellHeight, - Decoration dateDecoration) { - final BoxPainter boxPainter = dateDecoration.createBoxPainter(); - boxPainter.paint( - canvas, - Offset(xPosition + padding, yPosition + padding), - ImageConfiguration( - size: Size(cellWidth - (2 * padding), cellHeight - (2 * padding)))); -} - -void _drawCurrentDate( - Canvas canvas, - _IMonthViewPainter monthViewPainter, - double xPosition, - double yPosition, - double padding, - double cellWidth, - double cellHeight) { - monthViewPainter.selectionPainter = - monthViewPainter.selectionPainter ?? Paint(); - monthViewPainter.selectionPainter.color = - monthViewPainter.todayHighlightColor ?? - monthViewPainter.datePickerTheme.todayHighlightColor; - monthViewPainter.selectionPainter.isAntiAlias = true; - monthViewPainter.selectionPainter.strokeWidth = 1.0; - monthViewPainter.selectionPainter.style = PaintingStyle.stroke; - - switch (monthViewPainter.selectionShape) { - case DateRangePickerSelectionShape.circle: - { - final double centerXPosition = cellWidth / 2; - final double centerYPosition = cellHeight / 2; - final double radius = _getCellRadius( - monthViewPainter.selectionRadius, centerXPosition, centerYPosition); - canvas.drawCircle( - Offset(xPosition + centerXPosition, yPosition + centerYPosition), - radius, - monthViewPainter.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)), - monthViewPainter.selectionPainter); - } - } -} - -TextStyle _updateTextStyle( - _IMonthViewPainter monthViewPainter, - bool isNextMonth, - bool isPreviousMonth, - bool isCurrentDate, - bool isEnableDate, - bool isBlackedDate, - bool isWeekEnd, - bool isSpecialDate) { - final TextStyle currentDatesTextStyle = - monthViewPainter.cellStyle.textStyle ?? - monthViewPainter.datePickerTheme.activeDatesTextStyle; - if (isBlackedDate) { - return monthViewPainter.cellStyle.blackoutDateTextStyle ?? - (monthViewPainter.datePickerTheme.blackoutDatesTextStyle ?? - currentDatesTextStyle.copyWith( - decoration: TextDecoration.lineThrough)); - } - - if (isSpecialDate) { - return monthViewPainter.cellStyle.specialDatesTextStyle ?? - monthViewPainter.datePickerTheme.specialDatesTextStyle; - } - - if (!isEnableDate) { - return monthViewPainter.cellStyle.disabledDatesTextStyle ?? - monthViewPainter.datePickerTheme.disabledDatesTextStyle; - } - - if (isCurrentDate) { - return monthViewPainter.cellStyle.todayTextStyle ?? - monthViewPainter.datePickerTheme.todayTextStyle; - } - - if (isWeekEnd) { - return monthViewPainter.cellStyle.weekendTextStyle ?? - (monthViewPainter.datePickerTheme != null && - monthViewPainter.datePickerTheme.weekendDatesTextStyle != null - ? monthViewPainter.datePickerTheme.weekendDatesTextStyle - : currentDatesTextStyle); - } - - if (isNextMonth) { - return monthViewPainter.cellStyle.leadingDatesTextStyle ?? - monthViewPainter.datePickerTheme.leadingDatesTextStyle; - } else if (isPreviousMonth) { - return monthViewPainter.cellStyle.trailingDatesTextStyle ?? - monthViewPainter.datePickerTheme.trailingDatesTextStyle; - } - - return currentDatesTextStyle; -} - -Decoration _updateDecoration( - bool isNextMonth, - bool isPreviousMonth, - _IMonthViewPainter monthViewPainter, - isEnableDate, - isCurrentDate, - isBlackedDate, - DateTime date, - bool isWeekEnd, - bool isSpecialDate) { - final Decoration dateDecoration = monthViewPainter.cellStyle.cellDecoration; - - if (isBlackedDate) { - return monthViewPainter.cellStyle.blackoutDatesDecoration; - } - - if (isSpecialDate) { - return monthViewPainter.cellStyle.specialDatesDecoration; - } - - if (!isEnableDate) { - return monthViewPainter.cellStyle.disabledDatesDecoration; - } - - if (isCurrentDate) { - return monthViewPainter.cellStyle.todayCellDecoration ?? dateDecoration; - } - - if (isWeekEnd) { - return monthViewPainter.cellStyle.weekendDatesDecoration ?? dateDecoration; - } - - if (isNextMonth) { - return monthViewPainter.cellStyle.leadingDatesDecoration; - } else if (isPreviousMonth) { - return monthViewPainter.cellStyle.trailingDatesDecoration; - } - - return dateDecoration; -} - -List _getSemanticsBuilderForMonthView( - Size size, int rowCount, _IMonthViewPainter monthViewPainter) { - final List semanticsBuilder = - []; - double left, top; - Map leftAndTopValue; - int count = 1; - double width = size.width; - if (monthViewPainter.enableMultiView) { - count = 2; - width = (size.width - monthViewPainter.multiViewSpacing) / 2; - } - - final double cellWidth = width / _kNumberOfDaysInWeek; - final double cellHeight = size.height / rowCount; - final int datesCount = monthViewPainter.visibleDates.length ~/ count; - for (int j = 0; j < count; j++) { - final int currentViewIndex = - monthViewPainter.isRtl ? _getRtlIndex(count, j) : j; - left = monthViewPainter.isRtl ? width - cellWidth : 0; - top = 0; - final DateTime middleDate = - monthViewPainter.visibleDates[(j * datesCount) + (datesCount ~/ 2)]; - for (int i = 0; i < datesCount; i++) { - final DateTime currentDate = - monthViewPainter.visibleDates[(j * datesCount) + i]; - if (!_isDateAsCurrentMonthDate(middleDate, monthViewPainter.rowCount, - monthViewPainter.showLeadingAndTailingDates, currentDate)) { - leftAndTopValue = _getTopAndLeftValues( - monthViewPainter.isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; - continue; - } else if (_isDateWithInVisibleDates(monthViewPainter.visibleDates, - monthViewPainter.blackoutDates, currentDate)) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH( - (currentViewIndex * width) + - (currentViewIndex * monthViewPainter.multiViewSpacing) + - left, - top, - cellWidth, - cellHeight), - properties: SemanticsProperties( - label: - DateFormat('EEE, dd/MMMM/yyyy').format(currentDate).toString() + - ', Blackout date', - textDirection: TextDirection.ltr, - ), - )); - leftAndTopValue = _getTopAndLeftValues( - monthViewPainter.isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; - continue; - } else if (!_isEnabledDate( - monthViewPainter.minDate, - monthViewPainter.maxDate, - monthViewPainter.enablePastDates, - currentDate)) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH( - (currentViewIndex * width) + - (currentViewIndex * monthViewPainter.multiViewSpacing) + - left, - top, - cellWidth, - cellHeight), - properties: SemanticsProperties( - label: - DateFormat('EEE, dd/MMMM/yyyy').format(currentDate).toString() + - ', Disabled date', - textDirection: TextDirection.ltr, - ), - )); - leftAndTopValue = _getTopAndLeftValues( - monthViewPainter.isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; - continue; - } - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH( - (currentViewIndex * width) + - (currentViewIndex * monthViewPainter.multiViewSpacing) + - left, - top, - cellWidth, - cellHeight), - properties: SemanticsProperties( - label: DateFormat('EEE, dd/MMMM/yyyy').format(currentDate).toString(), - textDirection: TextDirection.ltr, - ), - )); - leftAndTopValue = _getTopAndLeftValues( - monthViewPainter.isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; - } - } - - return semanticsBuilder; -} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/interface.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/interface.dart deleted file mode 100644 index 02afb3e23..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/interface.dart +++ /dev/null @@ -1,59 +0,0 @@ -part of datepicker; - -abstract class _IMonthViewPainter extends CustomPainter { - _IMonthViewPainter({ValueNotifier selectionNotifier}) - : super(repaint: selectionNotifier); - - int rowCount; - DateRangePickerMonthCellStyle cellStyle; - List visibleDates; - bool isRtl; - Color todayHighlightColor; - SfDateRangePickerThemeData datePickerTheme; - DateTime minDate; - DateTime maxDate; - bool enablePastDates; - bool showLeadingAndTailingDates; - List blackoutDates; - List specialDates; - List weekendDays; - DateRangePickerSelectionShape selectionShape; - double selectionRadius; - ValueNotifier selectionNotifier; - Paint selectionPainter; - TextPainter textPainter; - Offset mouseHoverPosition; - bool enableMultiView; - double multiViewSpacing; - TextStyle selectionTextStyle; - TextStyle rangeTextStyle; - Color selectionColor; - Color startRangeSelectionColor; - Color endRangeSelectionColor; - Color rangeSelectionColor; - double textScaleFactor; - static const double _selectionPadding = 2; - - void _updateSelection(_PickerStateArgs details); - - @override - void paint(Canvas canvas, Size size); - - @override - bool shouldRepaint(CustomPainter oldDelegate); - - List _getSelectedIndexValues(int viewStartIndex, int viewEndIndex); - - TextStyle _drawSelection(Canvas canvas, double x, double y, int index, - TextStyle selectionTextStyle, TextStyle selectionRangeTextStyle); - - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the list - /// of custom painter semantics which contains the rect area and the semantics - /// properties for accessibility - @override - SemanticsBuilderCallback get semanticsBuilder; - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate); -} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/multi_range_selection.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/multi_range_selection.dart deleted file mode 100644 index 52b5b668d..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/multi_range_selection.dart +++ /dev/null @@ -1,263 +0,0 @@ -part of datepicker; - -class _MonthViewMultiRangeSelectionPainter extends _IMonthViewPainter { - _MonthViewMultiRangeSelectionPainter( - 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.selectedRanges, - this.selectionShape, - this.selectionRadius, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionNotifier, - this.textScaleFactor) - : super(selectionNotifier: selectionNotifier); - - @override - final int rowCount; - @override - final DateRangePickerMonthCellStyle cellStyle; - @override - final List visibleDates; - @override - final SfDateRangePickerThemeData datePickerTheme; - @override - final bool isRtl; - @override - final Color todayHighlightColor; - @override - final DateTime minDate; - @override - final DateTime maxDate; - @override - final bool enablePastDates; - @override - final bool showLeadingAndTailingDates; - @override - final List blackoutDates; - @override - final List specialDates; - @override - final List weekendDays; - @override - final DateRangePickerSelectionShape selectionShape; - @override - final double selectionRadius; - @override - final bool enableMultiView; - @override - final double multiViewSpacing; - @override - final Offset mouseHoverPosition; - @override - final TextStyle selectionTextStyle; - @override - final TextStyle rangeTextStyle; - @override - final Color selectionColor; - @override - final Color startRangeSelectionColor; - @override - final Color endRangeSelectionColor; - @override - final Color rangeSelectionColor; - List selectedRanges; - @override - ValueNotifier selectionNotifier; - @override - Paint selectionPainter; - @override - TextPainter textPainter; - @override - final double textScaleFactor; - double _cellWidth, _cellHeight; - double _centerXPosition, _centerYPosition; - List> _selectedRangesIndex; - - @override - TextStyle _drawSelection(Canvas canvas, double x, double y, int index, - TextStyle selectionTextStyle, TextStyle selectionRangeTextStyle) { - bool isSelectedDate = false; - bool isStartRange = false; - bool isEndRange = false; - bool isBetweenRange = false; - for (int j = 0; j < _selectedRangesIndex.length; j++) { - final List rangeIndex = _selectedRangesIndex[j]; - if (!rangeIndex.contains(index)) { - continue; - } - - if (rangeIndex.length == 1) { - isSelectedDate = true; - } else if (rangeIndex[0] == index) { - if (isRtl) { - isEndRange = true; - } else { - isStartRange = true; - } - } else if (rangeIndex[rangeIndex.length - 1] == index) { - if (isRtl) { - isStartRange = true; - } else { - isEndRange = true; - } - } else { - isBetweenRange = true; - } - - break; - } - - 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); - } else if (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); - } else if (isBetweenRange) { - return _drawBetweenSelection(canvas, this, _cellWidth, _cellHeight, - radius, x, y, heightDifference, selectionRangeTextStyle); - } - - return selectionTextStyle; - } - - @override - List _getSelectedIndexValues(int viewStartIndex, int viewEndIndex) { - final List selectedIndex = []; - _selectedRangesIndex = >[]; - if (selectedRanges != null) { - for (int j = 0; j < selectedRanges.length; j++) { - final PickerDateRange range = selectedRanges[j]; - final DateTime startDate = range.startDate; - final DateTime endDate = range.endDate ?? range.startDate; - final List rangeIndex = _getSelectedRangeIndex( - startDate, endDate, visibleDates, - monthStartIndex: viewStartIndex, monthEndIndex: viewEndIndex); - for (int i = 0; i < rangeIndex.length; i++) { - selectedIndex.add(rangeIndex[i]); - } - - _selectedRangesIndex.add(rangeIndex); - } - } - - return selectedIndex; - } - - @override - void paint(Canvas canvas, Size size) { - _cellWidth = size.width / _kNumberOfDaysInWeek; - if (enableMultiView) { - _cellWidth = (size.width - multiViewSpacing) / (_kNumberOfDaysInWeek * 2); - } - - _cellHeight = size.height / rowCount; - _centerXPosition = _cellWidth / 2; - _centerYPosition = _cellHeight / 2; - _drawMonthCellsAndSelection(canvas, size, this, _cellWidth, _cellHeight); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _MonthViewMultiRangeSelectionPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.rowCount != rowCount || - oldWidget.todayHighlightColor != todayHighlightColor || - oldWidget.enablePastDates != enablePastDates || - oldWidget.showLeadingAndTailingDates != showLeadingAndTailingDates || - oldWidget.cellStyle != cellStyle || - oldWidget.minDate != minDate || - oldWidget.enableMultiView != enableMultiView || - oldWidget.multiViewSpacing != multiViewSpacing || - oldWidget.maxDate != maxDate || - oldWidget.selectedRanges != selectedRanges || - oldWidget.blackoutDates != blackoutDates || - oldWidget.specialDates != specialDates || - oldWidget.selectionShape != selectionShape || - oldWidget.selectionRadius != selectionRadius || - oldWidget.rangeSelectionColor != rangeSelectionColor || - oldWidget.endRangeSelectionColor != endRangeSelectionColor || - oldWidget.startRangeSelectionColor != startRangeSelectionColor || - oldWidget.selectionColor != selectionColor || - oldWidget.rangeTextStyle != rangeTextStyle || - oldWidget.selectionTextStyle != selectionTextStyle || - oldWidget.isRtl != isRtl || - oldWidget.datePickerTheme != datePickerTheme || - oldWidget.textScaleFactor != textScaleFactor || - (kIsWeb && oldWidget.mouseHoverPosition != mouseHoverPosition); - } - - @override - void _updateSelection(_PickerStateArgs details) { - if (_isDateRangesEquals(details._selectedRanges, selectedRanges)) { - return; - } - - selectedRanges = _cloneList(details._selectedRanges); - selectionNotifier.value = !selectionNotifier.value; - } - - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilderForMonthView(size, rowCount, this); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _MonthViewMultiRangeSelectionPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates; - } -} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/multi_selection.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/multi_selection.dart deleted file mode 100644 index dc157b6e3..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/multi_selection.dart +++ /dev/null @@ -1,201 +0,0 @@ -part of datepicker; - -class _MonthViewMultiSelectionPainter extends _IMonthViewPainter { - _MonthViewMultiSelectionPainter( - 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.selectedDates, - this.selectionShape, - this.selectionRadius, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionNotifier, - this.textScaleFactor) - : super(selectionNotifier: selectionNotifier); - - @override - final int rowCount; - @override - final DateRangePickerMonthCellStyle cellStyle; - @override - final List visibleDates; - @override - final bool isRtl; - @override - final Color todayHighlightColor; - @override - final SfDateRangePickerThemeData datePickerTheme; - @override - final DateTime minDate; - @override - final DateTime maxDate; - @override - final bool enablePastDates; - @override - final bool showLeadingAndTailingDates; - @override - final List blackoutDates; - @override - final List specialDates; - @override - final List weekendDays; - @override - final DateRangePickerSelectionShape selectionShape; - @override - final double selectionRadius; - @override - final bool enableMultiView; - @override - final double multiViewSpacing; - @override - final Offset mouseHoverPosition; - @override - final TextStyle selectionTextStyle; - @override - final TextStyle rangeTextStyle; - @override - final Color selectionColor; - @override - final Color startRangeSelectionColor; - @override - final Color endRangeSelectionColor; - @override - final Color rangeSelectionColor; - List selectedDates; - @override - ValueNotifier selectionNotifier; - @override - Paint selectionPainter; - @override - TextPainter textPainter; - @override - final double textScaleFactor; - double _cellWidth, _cellHeight; - double _centerXPosition, _centerYPosition; - - @override - 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); - } - break; - case DateRangePickerSelectionShape.rectangle: - { - _drawFillSelection( - canvas, x, y, _cellWidth, _cellHeight, selectionPainter); - } - } - - return selectionTextStyle; - } - - @override - List _getSelectedIndexValues(int viewStartIndex, int viewEndIndex) { - final List selectedIndex = []; - if (selectedDates != null) { - for (int j = 0; j < selectedDates.length; j++) { - final DateTime date = selectedDates[j]; - if (!isDateWithInDateRange( - visibleDates[viewStartIndex], visibleDates[viewEndIndex], date)) { - continue; - } - - selectedIndex.add(_getSelectedIndex(date, visibleDates, - viewStartIndex: viewStartIndex)); - } - } - - return selectedIndex; - } - - @override - void paint(Canvas canvas, Size size) { - _cellWidth = size.width / _kNumberOfDaysInWeek; - if (enableMultiView) { - _cellWidth = (size.width - multiViewSpacing) / (_kNumberOfDaysInWeek * 2); - } - - _cellHeight = size.height / rowCount; - _centerXPosition = _cellWidth / 2; - _centerYPosition = _cellHeight / 2; - _drawMonthCellsAndSelection(canvas, size, this, _cellWidth, _cellHeight); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _MonthViewMultiSelectionPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.rowCount != rowCount || - oldWidget.todayHighlightColor != todayHighlightColor || - oldWidget.enablePastDates != enablePastDates || - oldWidget.showLeadingAndTailingDates != showLeadingAndTailingDates || - oldWidget.cellStyle != cellStyle || - oldWidget.minDate != minDate || - oldWidget.maxDate != maxDate || - oldWidget.enableMultiView != enableMultiView || - oldWidget.multiViewSpacing != multiViewSpacing || - oldWidget.selectedDates != selectedDates || - oldWidget.blackoutDates != blackoutDates || - oldWidget.specialDates != specialDates || - oldWidget.selectionShape != selectionShape || - oldWidget.selectionRadius != selectionRadius || - oldWidget.rangeSelectionColor != rangeSelectionColor || - oldWidget.endRangeSelectionColor != endRangeSelectionColor || - oldWidget.startRangeSelectionColor != startRangeSelectionColor || - oldWidget.selectionColor != selectionColor || - oldWidget.rangeTextStyle != rangeTextStyle || - oldWidget.selectionTextStyle != selectionTextStyle || - oldWidget.isRtl != isRtl || - oldWidget.datePickerTheme != datePickerTheme || - oldWidget.textScaleFactor != textScaleFactor || - (kIsWeb && oldWidget.mouseHoverPosition != mouseHoverPosition); - } - - @override - void _updateSelection(_PickerStateArgs details) { - if (_isDateCollectionEquals(details._selectedDates, selectedDates)) { - return; - } - - selectedDates = _cloneList(details._selectedDates); - selectionNotifier.value = !selectionNotifier.value; - } - - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilderForMonthView(size, rowCount, this); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _MonthViewMultiSelectionPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates; - } -} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/range_selection.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/range_selection.dart deleted file mode 100644 index 9e844170e..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/range_selection.dart +++ /dev/null @@ -1,244 +0,0 @@ -part of datepicker; - -class _MonthViewRangeSelectionPainter extends _IMonthViewPainter { - _MonthViewRangeSelectionPainter( - 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.selectedRange, - this.selectionShape, - this.selectionRadius, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionNotifier, - this.textScaleFactor) - : super(selectionNotifier: selectionNotifier); - - @override - final int rowCount; - @override - final DateRangePickerMonthCellStyle cellStyle; - @override - final List visibleDates; - @override - final SfDateRangePickerThemeData datePickerTheme; - @override - final bool isRtl; - @override - final Color todayHighlightColor; - @override - final DateTime minDate; - @override - final DateTime maxDate; - @override - final bool enablePastDates; - @override - final bool showLeadingAndTailingDates; - @override - final List blackoutDates; - @override - final List weekendDays; - @override - final List specialDates; - @override - final DateRangePickerSelectionShape selectionShape; - @override - final double selectionRadius; - @override - final bool enableMultiView; - @override - final double multiViewSpacing; - @override - final Offset mouseHoverPosition; - @override - final TextStyle selectionTextStyle; - @override - final TextStyle rangeTextStyle; - @override - final Color selectionColor; - @override - final Color startRangeSelectionColor; - @override - final Color endRangeSelectionColor; - @override - final Color rangeSelectionColor; - PickerDateRange selectedRange; - @override - ValueNotifier selectionNotifier; - @override - Paint selectionPainter; - @override - TextPainter textPainter; - @override - final double textScaleFactor; - double _cellWidth, _cellHeight; - double _centerXPosition, _centerYPosition; - List _selectedIndex; - - @override - TextStyle _drawSelection(Canvas canvas, double x, double y, int index, - TextStyle selectionTextStyle, TextStyle selectionRangeTextStyle) { - bool isSelectedDate = false; - bool isStartRange = false; - bool isEndRange = false; - bool isBetweenRange = false; - if (_selectedIndex.length == 1) { - isSelectedDate = true; - } else if (_selectedIndex[0] == index) { - if (isRtl) { - isEndRange = true; - } else { - isStartRange = true; - } - } else if (_selectedIndex[_selectedIndex.length - 1] == index) { - if (isRtl) { - isStartRange = true; - } else { - isEndRange = true; - } - } else { - isBetweenRange = true; - } - - 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); - } else if (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); - } else if (isBetweenRange) { - return _drawBetweenSelection(canvas, this, _cellWidth, _cellHeight, - radius, x, y, heightDifference, selectionRangeTextStyle); - } - - return selectionTextStyle; - } - - @override - List _getSelectedIndexValues(int viewStartIndex, int viewEndIndex) { - _selectedIndex = []; - if (selectedRange != null) { - final DateTime startDate = selectedRange.startDate; - final DateTime endDate = selectedRange.endDate ?? selectedRange.startDate; - _selectedIndex = _getSelectedRangeIndex(startDate, endDate, visibleDates, - monthStartIndex: viewStartIndex, monthEndIndex: viewEndIndex); - } - - return _selectedIndex; - } - - @override - void paint(Canvas canvas, Size size) { - _cellWidth = size.width / _kNumberOfDaysInWeek; - if (enableMultiView) { - _cellWidth = (size.width - multiViewSpacing) / (_kNumberOfDaysInWeek * 2); - } - - _cellHeight = size.height / rowCount; - _centerXPosition = _cellWidth / 2; - _centerYPosition = _cellHeight / 2; - _drawMonthCellsAndSelection(canvas, size, this, _cellWidth, _cellHeight); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _MonthViewRangeSelectionPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.rowCount != rowCount || - oldWidget.todayHighlightColor != todayHighlightColor || - oldWidget.enablePastDates != enablePastDates || - oldWidget.showLeadingAndTailingDates != showLeadingAndTailingDates || - oldWidget.cellStyle != cellStyle || - oldWidget.minDate != minDate || - oldWidget.maxDate != maxDate || - oldWidget.enableMultiView != enableMultiView || - oldWidget.multiViewSpacing != multiViewSpacing || - oldWidget.selectedRange != selectedRange || - oldWidget.blackoutDates != blackoutDates || - oldWidget.specialDates != specialDates || - oldWidget.selectionShape != selectionShape || - oldWidget.selectionRadius != selectionRadius || - oldWidget.rangeSelectionColor != rangeSelectionColor || - oldWidget.endRangeSelectionColor != endRangeSelectionColor || - oldWidget.startRangeSelectionColor != startRangeSelectionColor || - oldWidget.selectionColor != selectionColor || - oldWidget.rangeTextStyle != rangeTextStyle || - oldWidget.selectionTextStyle != selectionTextStyle || - oldWidget.isRtl != isRtl || - oldWidget.datePickerTheme != datePickerTheme || - oldWidget.textScaleFactor != textScaleFactor || - (kIsWeb && oldWidget.mouseHoverPosition != mouseHoverPosition); - } - - @override - void _updateSelection(_PickerStateArgs details) { - if (_isRangeEquals(details._selectedRange, selectedRange)) { - return; - } - - selectedRange = details._selectedRange; - selectionNotifier.value = !selectionNotifier.value; - } - - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilderForMonthView(size, rowCount, this); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _MonthViewRangeSelectionPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates; - } -} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/single_selection.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/single_selection.dart deleted file mode 100644 index cbb4b7e91..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view/single_selection.dart +++ /dev/null @@ -1,197 +0,0 @@ -part of datepicker; - -class _MonthViewSingleSelectionPainter extends _IMonthViewPainter { - _MonthViewSingleSelectionPainter( - 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.selectedDate, - this.selectionShape, - this.selectionRadius, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionNotifier, - this.textScaleFactor) - : super(selectionNotifier: selectionNotifier); - - @override - final int rowCount; - @override - final DateRangePickerMonthCellStyle cellStyle; - @override - final List visibleDates; - @override - final SfDateRangePickerThemeData datePickerTheme; - @override - final bool isRtl; - @override - final Color todayHighlightColor; - @override - final DateTime minDate; - @override - final DateTime maxDate; - @override - final bool enablePastDates; - @override - final bool showLeadingAndTailingDates; - @override - final List blackoutDates; - @override - final List specialDates; - @override - final List weekendDays; - @override - final DateRangePickerSelectionShape selectionShape; - @override - final double selectionRadius; - @override - final bool enableMultiView; - @override - final double multiViewSpacing; - @override - final Offset mouseHoverPosition; - @override - final TextStyle selectionTextStyle; - @override - final TextStyle rangeTextStyle; - @override - final Color selectionColor; - @override - final Color startRangeSelectionColor; - @override - final Color endRangeSelectionColor; - @override - final Color rangeSelectionColor; - DateTime selectedDate; - @override - ValueNotifier selectionNotifier; - @override - final double textScaleFactor; - @override - Paint selectionPainter; - @override - TextPainter textPainter; - double _cellWidth, _cellHeight; - double _centerXPosition, _centerYPosition; - - @override - 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); - } - break; - case DateRangePickerSelectionShape.rectangle: - { - _drawFillSelection( - canvas, x, y, _cellWidth, _cellHeight, selectionPainter); - } - } - - return selectionTextStyle; - } - - @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); - selectedIndex.add(index); - } - } - - return selectedIndex; - } - - @override - void paint(Canvas canvas, Size size) { - _cellWidth = size.width / _kNumberOfDaysInWeek; - if (enableMultiView) { - _cellWidth = (size.width - multiViewSpacing) / (_kNumberOfDaysInWeek * 2); - } - - _cellHeight = size.height / rowCount; - _centerXPosition = _cellWidth / 2; - _centerYPosition = _cellHeight / 2; - _drawMonthCellsAndSelection(canvas, size, this, _cellWidth, _cellHeight); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _MonthViewSingleSelectionPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.rowCount != rowCount || - oldWidget.todayHighlightColor != todayHighlightColor || - oldWidget.enablePastDates != enablePastDates || - oldWidget.showLeadingAndTailingDates != showLeadingAndTailingDates || - oldWidget.cellStyle != cellStyle || - oldWidget.minDate != minDate || - oldWidget.enableMultiView != enableMultiView || - oldWidget.multiViewSpacing != multiViewSpacing || - oldWidget.maxDate != maxDate || - oldWidget.selectedDate != selectedDate || - oldWidget.blackoutDates != blackoutDates || - oldWidget.specialDates != specialDates || - oldWidget.selectionShape != selectionShape || - oldWidget.selectionRadius != selectionRadius || - oldWidget.rangeSelectionColor != rangeSelectionColor || - oldWidget.endRangeSelectionColor != endRangeSelectionColor || - oldWidget.startRangeSelectionColor != startRangeSelectionColor || - oldWidget.selectionColor != selectionColor || - oldWidget.rangeTextStyle != rangeTextStyle || - oldWidget.selectionTextStyle != selectionTextStyle || - oldWidget.isRtl != isRtl || - oldWidget.datePickerTheme != datePickerTheme || - oldWidget.textScaleFactor != textScaleFactor || - (kIsWeb && oldWidget.mouseHoverPosition != mouseHoverPosition); - } - - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilderForMonthView(size, rowCount, this); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _MonthViewSingleSelectionPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates; - } - - @override - void _updateSelection(_PickerStateArgs details) { - if (isSameDate(details._selectedDate, selectedDate)) { - return; - } - - selectedDate = details._selectedDate; - selectionNotifier.value = !selectionNotifier.value; - } -} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_controller.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_controller.dart deleted file mode 100644 index 19271ce3d..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_controller.dart +++ /dev/null @@ -1,636 +0,0 @@ -part of datepicker; - -typedef DateRangePickerValueChangedCallback = void Function(String); - -const String _kSelectedDateString = 'selectedDate'; -const String _kSelectedDatesString = 'selectedDates'; -const String _kSelectedRangeString = 'selectedRange'; -const String _kSelectedRangesString = 'selectedRanges'; -const String _kDisplayDateString = 'displayDate'; -const String _kViewString = 'view'; - -/// Notifier used to notify the when the objects properties changed. -class DateRangePickerValueChangeNotifier { - List _listeners; - - /// Calls the listener every time the controller's property changed. - /// - /// Listeners can be removed with [removePropertyChangedListener]. - void addPropertyChangedListener( - DateRangePickerValueChangedCallback listener) { - _listeners ??= []; - _listeners.add(listener); - } - - /// remove the listener used for notify the data source changes. - /// - /// Stop calling the listener every time in controller's property changed. - /// - /// If `listener` is not currently registered as a listener, this method does - /// nothing. - /// - /// Listeners can be added with [addPropertyChangedListener]. - void removePropertyChangedListener( - DateRangePickerValueChangedCallback listener) { - if (_listeners == null) { - return; - } - - _listeners.remove(listener); - } - - /// Call all the registered listeners. - /// - /// Call this method whenever the object changes, to notify any clients the - /// object may have. Listeners that are added during this iteration will not - /// be visited. Listeners that are removed during this iteration will not be - /// visited after they are removed. - /// - /// This method must not be called after [dispose] has been called. - /// - /// Surprising behavior can result when reentrantly removing a listener (i.e. - /// in response to a notification) that has been registered multiple times. - /// See the discussion at [removePropertyChangedListener]. - void notifyPropertyChangedListeners(String value) { - if (_listeners == null) { - return; - } - - for (final DateRangePickerValueChangedCallback listener in _listeners) { - if (listener != null) { - listener(value); - } - } - } - - /// Discards any resources used by the object. After this is called, the - /// object is not in a usable state and should be discarded (calls to - /// [addListener] and [removeListener] will throw after the object is - /// disposed). - /// - /// This method should only be called by the object's owner. - @mustCallSuper - void dispose() { - _listeners = null; - } -} - -/// An object that used for programmatic date navigation, date and range -/// selection and view switching in [SfDateRangePicker]. -/// -/// A [DateRangePickerController] served for several purposes. It can be used -/// to selected dates and ranges programmatically on [SfDateRangePicker] by -/// using the[controller.selectedDate], [controller.selectedDates], -/// [controller.selectedRange], [controller.selectedRanges]. It can be used to -/// change the [SfDateRangePicker] view by using the [controller.view] property. -/// It can be used to navigate to specific date by using the -/// [controller.displayDate] property. -/// -/// ## Listening to property changes: -/// The [DateRangePickerController] is a listenable. It notifies it's listeners -/// whenever any of attached [SfDateRangePicker]`s selected date, display date -/// and view changed (i.e: selecting a different date, swiping to next/previous -/// view and navigates to different view] in in [SfDateRangePicker]. -/// -/// ## Navigates to different view: -/// The [SfDateRangePicker] visible view can be changed by using the -/// [Controller.view] property, the property allow to change the view of -/// [SfDateRangePicker] programmatically on initial load and in rum time. -/// -/// ## Programmatic selection: -/// In [SfDateRangePicker] selecting dates programmatically can be achieved by -/// using the [controller.selectedDate], [controller.selectedDates], -/// [controller.selectedRange], [controller.selectedRanges] which allows to -/// select the dates or ranges programmatically on [SfDateRangePicker] on -/// initial load and in run time. -/// -/// See also: [DateRangePickerSelectionMode] -/// -/// Defaults to null. -/// -/// This example demonstrates how to use the [SfDateRangePickerController] for -/// [SfDateRangePicker]. -/// -/// ``` dart -/// -///class MyApp extends StatefulWidget { -/// @override -/// MyAppState createState() => MyAppState(); -///} -/// -///class MyAppState extends State { -/// DateRangePickerController _pickerController; -/// -/// @override -/// void initState() { -/// _pickerController = DateRangePickerController(); -/// _pickerController.selectedDates = [ -/// DateTime.now().add(Duration(days: 2)), -/// DateTime.now().add(Duration(days: 4)), -/// DateTime.now().add(Duration(days: 7)), -/// DateTime.now().add(Duration(days: 11)) -/// ]; -/// _pickerController.displayDate = DateTime.now(); -/// _pickerController.addPropertyChangedListener(handlePropertyChange); -/// super.initState(); -/// } -/// -/// void handlePropertyChange(String propertyName) { -/// if (propertyName == 'selectedDates') { -/// final List selectedDates = _pickerController.selectedDates; -/// } else if (propertyName == 'displayDate') { -/// final DateTime displayDate = _pickerController.displayDate; -/// } -/// } -/// -/// @override -/// Widget build(BuildContext context) { -/// return MaterialApp( -/// home: Scaffold( -/// body: SfDateRangePicker( -/// view: DateRangePickerView.month, -/// controller: _pickerController, -/// selectionMode: DateRangePickerSelectionMode.multiple, -/// ), -/// ), -/// ); -/// } -///} -/// -/// ``` -class DateRangePickerController extends DateRangePickerValueChangeNotifier { - DateTime _selectedDate; - List _selectedDates; - PickerDateRange _selectedRange; - List _selectedRanges; - DateTime _displayDate; - DateRangePickerView _view; - - /// The selected date in the [SfDateRangePicker]. - /// - /// It is only applicable when the [selectionMode] set as - /// [DateRangePickerSelectionMode.single] for other selection modes this - /// property will return as null. - DateTime get selectedDate => _selectedDate; - - /// Selects the given date programmatically in the [SfDateRangePicker] by - /// checking that the date falls in between the minimum and maximum date - /// range. - /// - /// _Note:_ If any date selected previously, will be removed and the selection - /// will be drawn to the date given in this property. - /// - /// If it is not [null] the widget will render the date selection for the date - /// set to this property, even the [SfDateRangePicker.initialSelectedDate] is - /// not null. - /// - /// It is only applicable when the [DateRangePickerSelectionMode] set as - /// [DateRangePickerSelectionMode.single]. - /// - /// ``` dart - /// - /// class MyAppState extends State { - /// DateRangePickerController _pickerController; - /// - /// @override - /// void initState() { - /// _pickerController = DateRangePickerController(); - /// _pickerController.selectedDate = DateTime.now().add((Duration( - /// days: 4))); - /// super.initState(); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return MaterialApp( - /// home: Scaffold( - /// body: SfDateRangePicker( - /// controller: _pickerController, - /// view: DateRangePickerView.month, - /// selectionMode: DateRangePickerSelectionMode.single, - /// showNavigationArrow: true, - /// ), - /// ), - /// ); - /// } - ///} - /// - /// ``` - set selectedDate(DateTime date) { - if (isSameDate(_selectedDate, date)) { - return; - } - - _selectedDate = date; - notifyPropertyChangedListeners(_kSelectedDateString); - } - - /// The list of dates selected in the [SfDateRangePicker]. - /// - /// It is only applicable when the [selectionMode] set as - /// [DateRangePickerSelectionMode.multiple] for other selection modes this - /// property will return as null. - List get selectedDates => _selectedDates; - - /// Selects the given dates programmatically in the [SfDateRangePicker] by - /// checking that the dates falls in between the minimum and maximum date - /// range. - /// - /// _Note:_ If any list of dates selected previously, will be removed and the - /// selection will be drawn to the dates set to this property. - /// - /// If it is not [null] the widget will render the date selection for the - /// dates set to this property, even the - /// [SfDateRangePicker.initialSelectedDates] is not null. - /// - /// It is only applicable when the [selectionMode] set as - /// [DateRangePickerSelectionMode.multiple]. - /// - /// ``` dart - /// - /// class MyAppState extends State { - /// DateRangePickerController _pickerController; - /// - /// @override - /// void initState() { - /// _pickerController = DateRangePickerController(); - /// _pickerController.selectedDates = [ - /// DateTime.now().add((Duration(days: 4))), - /// DateTime.now().add((Duration(days: 7))), - /// DateTime.now().add((Duration(days: 8))) - /// ]; - /// super.initState(); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return MaterialApp( - /// home: Scaffold( - /// body: SfDateRangePicker( - /// controller: _pickerController, - /// view: DateRangePickerView.month, - /// selectionMode: DateRangePickerSelectionMode.multiple, - /// showNavigationArrow: true, - /// ), - /// ), - /// ); - /// } - ///} - /// - /// ``` - set selectedDates(List dates) { - if (_isDateCollectionEquals(_selectedDates, dates)) { - return; - } - - _selectedDates = _cloneList(dates); - notifyPropertyChangedListeners(_kSelectedDatesString); - } - - /// selected date range in the [SfDateRangePicker]. - /// - /// It is only applicable when the [selectionMode] set as - /// [DateRangePickerSelectionMode.range] for other selection modes this - /// property will return as null. - PickerDateRange get selectedRange => _selectedRange; - - /// Selects the given date range programmatically in the [SfDateRangePicker] - /// by checking that the range of dates falls in between the minimum and - /// maximum date range. - /// - /// _Note:_ If any date range selected previously, will be removed and the - /// selection will be drawn to the range of dates set to this property. - /// - /// If it is not [null] the widget will render the date selection for the - /// range set to this property, even the - /// [SfDateRangePicker.initialSelectedRange] is not null. - /// - /// It is only applicable when the [selectionMode] set as - /// [DateRangePickerSelectionMode.range]. - /// - /// ``` dart - /// - /// class MyAppState extends State { - /// DateRangePickerController _pickerController; - /// - /// @override - /// void initState() { - /// _pickerController = DateRangePickerController(); - /// _pickerController.selectedRange = PickerDateRange( - /// DateTime.now().add(Duration(days: 4)), - /// DateTime.now().add(Duration(days: 5))); - /// super.initState(); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return MaterialApp( - /// home: Scaffold( - /// body: SfDateRangePicker( - /// controller: _pickerController, - /// view: DateRangePickerView.month, - /// selectionMode: DateRangePickerSelectionMode.range, - /// ), - /// ), - /// ); - /// } - ///} - /// - /// ``` - set selectedRange(PickerDateRange range) { - if (_isRangeEquals(_selectedRange, range)) { - return; - } - - _selectedRange = range; - notifyPropertyChangedListeners(_kSelectedRangeString); - } - - /// List of selected ranges in the [SfDateRangePicker]. - /// - /// It is only applicable when the [selectionMode] set as - /// [DateRangePickerSelectionMode.multiRange] for other selection modes this - /// property will return as null. - List get selectedRanges => _selectedRanges; - - /// Selects the given date ranges programmatically in the [SfDateRangePicker] - /// by checking that the ranges of dates falls in between the minimum and - /// maximum date range. - /// - /// If it is not [null] the widget will render the date selection for the - /// ranges set to this property, even the - /// [SfDateRangePicker.initialSelectedRanges] is not null. - /// - /// _Note:_ If any date ranges selected previously, will be removed and the - /// selection will be drawn to the ranges of dates set to this property. - /// - /// It is only applicable when the [selectionMode] set as - /// [DateRangePickerSelectionMode.multiRange]. - /// - /// ``` dart - /// - /// class MyAppState extends State { - /// DateRangePickerController _pickerController; - /// - /// @override - /// void initState() { - /// _pickerController = DateRangePickerController(); - /// _pickerController.selectedRanges = [ - /// PickerDateRange(DateTime.now().subtract(Duration(days: 4)), - /// DateTime.now().add(Duration(days: 4))), - /// PickerDateRange(DateTime.now().add(Duration(days: 11)), - /// DateTime.now().add(Duration(days: 16))) - /// ]; - /// super.initState(); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return MaterialApp( - /// home: Scaffold( - /// body: SfDateRangePicker( - /// controller: _pickerController, - /// view: DateRangePickerView.month, - /// selectionMode: DateRangePickerSelectionMode.multiRange, - /// ), - /// ), - /// ); - /// } - ///} - /// - /// ``` - set selectedRanges(List ranges) { - if (_isDateRangesEquals(_selectedRanges, ranges)) { - return; - } - - _selectedRanges = _cloneList(ranges); - notifyPropertyChangedListeners(_kSelectedRangesString); - } - - /// The first date of the current visible view month, when the - /// [MonthViewSettings.numberOfWeeksInView] set with default value 6. - /// - /// If the [MonthViewSettings.numberOfWeeksInView] property set with value - /// other then 6, this will return the first visible date of the current - /// month. - DateTime get displayDate => _displayDate; - - /// Navigates to the given date programmatically without any animation in the - /// [SfDateRangePicker] by checking that the date falls in between the - /// [SfDateRangePicker.minDate] and [SfDateRangePicker.maxDate] date range. - /// - /// If the date falls beyond the [SfDateRangePicker.minDate] and - /// [SfDateRangePicker.maxDate] the widget will move the widgets min or max - /// date. - /// - /// - /// ``` dart - /// - /// class MyAppState extends State { - /// DateRangePickerController _pickerController; - /// - /// @override - /// void initState() { - /// _pickerController = DateRangePickerController(); - /// _pickerController.displayDate = DateTime(2022, 02, 05); - /// super.initState(); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return MaterialApp( - /// home: Scaffold( - /// body: SfDateRangePicker( - /// controller: _pickerController, - /// view: DateRangePickerView.month, - /// selectionMode: DateRangePickerSelectionMode.single, - /// ), - /// ), - /// ); - /// } - ///} - /// - /// ``` - set displayDate(DateTime date) { - if (isSameDate(_displayDate, date)) { - return; - } - - _displayDate = date; - notifyPropertyChangedListeners(_kDisplayDateString); - } - - /// The current visible [DateRangePickerView] of [SfDateRangePicker]. - DateRangePickerView get view => _view; - - /// Set the [SfDateRangePickerView] for the [SfDateRangePicker]. - /// - /// - /// The [SfDateRangePicker] will display the view sets to this property. - /// - /// ```dart - /// - /// class MyAppState extends State { - /// DateRangePickerController _pickerController; - /// - /// @override - /// void initState() { - /// _pickerController = DateRangePickerController(); - /// _pickerController.view = DateRangePickerView.year; - /// super.initState(); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return MaterialApp( - /// home: Scaffold( - /// body: SfDateRangePicker( - /// controller: _pickerController, - /// view: DateRangePickerView.month, - /// selectionMode: DateRangePickerSelectionMode.single, - /// ), - /// ), - /// ); - /// } - ///} - /// - /// ``` - set view(DateRangePickerView value) { - if (_view == value) { - return; - } - - _view = value; - notifyPropertyChangedListeners(_kViewString); - } - - VoidCallback _forward; - - /// Moves to the next view programmatically with animation by checking that - /// the next view dates falls between the minimum and maximum date range. - /// - /// _Note:_ If the current view has the maximum date range, it will not move - /// to the next view. - /// - /// ```dart - /// - /// class MyApp extends StatefulWidget { - /// @override - /// MyAppState createState() => MyAppState(); - ///} - /// - ///class MyAppState extends State { - /// DateRangePickerController _pickerController; - /// - /// @override - /// void initState() { - /// _pickerController = DateRangePickerController(); - /// super.initState(); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return MaterialApp( - /// home: Scaffold( - /// appBar: AppBar( - /// actions: [ - /// IconButton( - /// icon: Icon(Icons.arrow_forward), - /// onPressed: () { - /// _pickerController.forward(); - /// }, - /// ) - /// ], - /// title: Text('Date Range Picker Demo'), - /// leading: IconButton( - /// icon: Icon(Icons.arrow_back), - /// onPressed: () { - /// _pickerController.backward(); - /// }, - /// ), - /// ), - /// body: SfDateRangePicker( - /// controller: _pickerController, - /// view: DateRangePickerView.month, - /// selectionMode: DateRangePickerSelectionMode.single, - /// ), - /// ), - /// ); - /// } - ///} - /// - /// ``` - void forward() { - if (_forward == null) { - return; - } - - _forward(); - } - - VoidCallback _backward; - - /// Moves to the previous view programmatically with animation by checking - /// that the previous view dates falls between the minimum and maximum date - /// range. - /// - /// _Note:_ If the current view has the minimum date range, it will not move - /// to the previous view. - /// - /// ```dart - /// - /// class MyApp extends StatefulWidget { - /// @override - /// MyAppState createState() => MyAppState(); - ///} - /// - ///class MyAppState extends State { - /// DateRangePickerController _pickerController; - /// - /// @override - /// void initState() { - /// _pickerController = DateRangePickerController(); - /// super.initState(); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return MaterialApp( - /// home: Scaffold( - /// appBar: AppBar( - /// actions: [ - /// IconButton( - /// icon: Icon(Icons.arrow_forward), - /// onPressed: () { - /// _pickerController.forward(); - /// }, - /// ) - /// ], - /// title: Text('Date Range Picker Demo'), - /// leading: IconButton( - /// icon: Icon(Icons.arrow_back), - /// onPressed: () { - /// _pickerController.backward(); - /// }, - /// ), - /// ), - /// body: SfDateRangePicker( - /// controller: _pickerController, - /// view: DateRangePickerView.month, - /// selectionMode: DateRangePickerSelectionMode.single, - /// ), - /// ), - /// ); - /// } - ///} - /// - /// ``` - void backward() { - if (_backward == null) { - return; - } - - _backward(); - } -} 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 new file mode 100644 index 000000000..b9319f890 --- /dev/null +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_helper.dart @@ -0,0 +1,839 @@ +import 'package:flutter/foundation.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'date_picker_manager.dart'; +import 'hijri_date_picker_manager.dart'; + +/// Holds the static helper methods of the date picker. +class DateRangePickerHelper { + /// Return the index value based on RTL. + static int getRtlIndex(int count, int index) { + return count - index - 1; + } + + /// Calculate the visible dates count based on picker view + static int getViewDatesCount( + DateRangePickerView pickerView, int numberOfWeeks, bool isHijri) { + if (pickerView == null) { + return 0; + } + + if (pickerView == DateRangePickerView.month) { + if (isHijri) { + /// 6 used to render the default number of weeks, since, Hijri type + /// doesn't support number of weeks in view. + return DateTime.daysPerWeek * 6; + } else { + return DateTime.daysPerWeek * numberOfWeeks; + } + } + + return 0; + } + + /// Checks both the ranges are equal or not. + static bool isRangeEquals(dynamic range1, dynamic range2) { + if ((range1 == null && range2 != null) || + (range2 == null && range1 != null)) { + return false; + } + + if (range1 == range2 || + (isSameDate(range1.startDate, range2.startDate) && + isSameDate(range1.endDate, range2.endDate))) { + return true; + } + + return false; + } + + /// Checks both the range collections are equal or not. + static bool isDateRangesEquals( + List rangeCollection1, List rangeCollection2) { + if (rangeCollection1 == rangeCollection2) { + return true; + } + + if ((rangeCollection1 == null && + rangeCollection2 != null && + rangeCollection2.isEmpty) || + (rangeCollection2 == null && + rangeCollection1 != null && + rangeCollection1.isEmpty)) { + return true; + } + + if ((rangeCollection1 == null && rangeCollection2 != null) || + (rangeCollection2 == null && rangeCollection1 != null) || + (rangeCollection1.length != rangeCollection2.length)) { + return false; + } + + for (int i = 0; i < rangeCollection1.length; i++) { + if (!isRangeEquals(rangeCollection1[i], rangeCollection2[i])) { + return false; + } + } + + return true; + } + + /// Calculate the next view visible start date based on picker view. + static dynamic getNextViewStartDate(DateRangePickerView pickerView, + int numberOfWeeksInView, dynamic date, bool isRtl, bool isHijri) { + if (pickerView == null) { + return date; + } + + if (isRtl != null && isRtl) { + return getPreviousViewStartDate( + pickerView, numberOfWeeksInView, date, false, isHijri); + } + + switch (pickerView) { + case DateRangePickerView.month: + { + return isHijri || numberOfWeeksInView == 6 + ? getNextMonthDate(date) + : addDuration(date, + Duration(days: numberOfWeeksInView * DateTime.daysPerWeek)); + } + case DateRangePickerView.year: + { + return getNextYearDate(date, 1, isHijri); + } + case DateRangePickerView.decade: + { + return getNextYearDate(date, 10, isHijri); + } + case DateRangePickerView.century: + { + return getNextYearDate(date, 100, isHijri); + } + } + + return date; + } + + /// Calculate the previous view visible start date based on calendar view. + static dynamic getPreviousViewStartDate(DateRangePickerView pickerView, + int numberOfWeeksInView, dynamic date, bool isRtl, bool isHijri) { + if (pickerView == null) { + return date; + } + + if (isRtl != null && isRtl) { + return getNextViewStartDate( + pickerView, numberOfWeeksInView, date, false, isHijri); + } + + switch (pickerView) { + case DateRangePickerView.month: + { + return isHijri || numberOfWeeksInView == 6 + ? getPreviousMonthDate(date) + : addDuration(date, + Duration(days: -numberOfWeeksInView * DateTime.daysPerWeek)); + } + case DateRangePickerView.year: + { + return getPreviousYearDate(date, 1, isHijri); + } + case DateRangePickerView.decade: + { + return getPreviousYearDate(date, 10, isHijri); + } + case DateRangePickerView.century: + { + return getPreviousYearDate(date, 100, isHijri); + } + } + + return date; + } + + /// Return the next view date of year view based on it offset value. + /// offset value as 1 for year view, 10 for decade view and 100 for + /// century view. + static dynamic getNextYearDate(dynamic date, int offset, bool isHijri) { + return getDate(((date.year ~/ offset) * offset) + offset, 1, 1, isHijri); + } + + /// Return the previous view date of year view based on it offset value. + /// offset value as 1 for year view, 10 for decade view and 100 for + /// century view. + static dynamic getPreviousYearDate(dynamic date, int offset, bool isHijri) { + return getDate(((date.year ~/ offset) * offset) - offset, 1, 1, isHijri); + } + + /// Return the month start date. + static dynamic getMonthStartDate(dynamic date, bool isHijri) { + return getDate(date.year, date.month, 1, isHijri); + } + + /// Return the month end date. + static dynamic getMonthEndDate(dynamic date) { + return subtractDuration(getNextMonthDate(date), const Duration(days: 1)); + } + + /// Return the index of the date in collection. + static int isDateIndexInCollection(List dates, dynamic date) { + if (dates == null || date == null) { + return -1; + } + + for (int i = 0; i < dates.length; i++) { + final dynamic visibleDate = dates[i]; + if (isSameDate(visibleDate, date)) { + return i; + } + } + + return -1; + } + + /// Checks both the date collection are equal or not. + static bool isDateCollectionEquals( + List datesCollection1, List datesCollection2) { + if (datesCollection1 == datesCollection2) { + return true; + } + + if ((datesCollection1 == null && + datesCollection2 != null && + datesCollection2.isEmpty) || + (datesCollection2 == null && + datesCollection1 != null && + datesCollection1.isEmpty)) { + return false; + } + + if ((datesCollection1 == null && datesCollection2 != null) || + (datesCollection2 == null && datesCollection1 != null) || + (datesCollection1.length != datesCollection2.length)) { + return false; + } + + for (int i = 0; i < datesCollection1.length; i++) { + if (!isSameDate(datesCollection1[i], datesCollection2[i])) { + return false; + } + } + + return true; + } + + /// 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) { + return isDateWithInDateRange(startDate, endDate, date) && + (enablePastDates || + (!enablePastDates && + isDateWithInDateRange(getToday(isHijri), endDate, date))); + } + + /// Check the date as current month date. + static bool isDateAsCurrentMonthDate(dynamic visibleDate, int rowCount, + bool showLeadingAndTrialingDates, dynamic date, bool isHijri) { + if ((rowCount == 6 && !showLeadingAndTrialingDates || isHijri) && + date.month != visibleDate.month) { + return false; + } + + return true; + } + + /// 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) { + final Map topAndLeft = { + 'left': left, + 'top': top + }; + if (isRtl) { + if (left.round() == cellWidth.round()) { + left = 0; + } else { + left -= cellWidth; + } + if (left < 0) { + left = width - cellWidth; + top += cellHeight; + } + } else { + left += cellWidth; + if (left + 1 >= width) { + top += cellHeight; + left = 0; + } + } + topAndLeft['left'] = left; + topAndLeft['top'] = top; + + return topAndLeft; + } + + /// Check the date placed in dates collection based on visible dates. + static bool isDateWithInVisibleDates( + List visibleDates, List dates, dynamic date) { + if (dates == null || dates.isEmpty) { + return false; + } + + final dynamic visibleStartDate = visibleDates[0]; + final dynamic visibleEndDate = visibleDates[visibleDates.length - 1]; + for (final dynamic currentDate in dates) { + if (!isDateWithInDateRange( + visibleStartDate, visibleEndDate, currentDate)) { + continue; + } + + if (isSameDate(currentDate, date)) { + return true; + } + } + + return false; + } + + /// Check the date week day placed in week end day collection. + static bool isWeekend(List weekendIndex, dynamic date) { + if (weekendIndex == null || weekendIndex.isEmpty) { + return false; + } + + return weekendIndex.contains(date.weekday); + } + + /// 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) { + if (isRtl) { + return canMoveToNextView(view, numberOfWeeksInView, maxDate, visibleDates, + enableMultiView, isHijri); + } else { + 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) { + if (isRtl) { + return canMoveToPreviousView(view, numberOfWeeksInView, minDate, + visibleDates, enableMultiView, isHijri); + } else { + 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) { + switch (view) { + case DateRangePickerView.month: + { + if (numberOfWeeksInView != 6 && !isHijri) { + DateTime prevViewDate = visibleDates[0]; + prevViewDate = + subtractDuration(prevViewDate, const Duration(days: 1)); + if (!isSameOrAfterDate(minDate, prevViewDate)) { + return false; + } + } else { + final dynamic currentDate = visibleDates[visibleDates.length ~/ + (enableMultiView != null && enableMultiView ? 4 : 2)]; + final dynamic previousDate = getPreviousMonthDate(currentDate); + if (((previousDate.month < minDate.month && + previousDate.year == minDate.year) || + previousDate.year < minDate.year)) { + return false; + } + } + } + break; + case DateRangePickerView.year: + case DateRangePickerView.decade: + case DateRangePickerView.century: + { + final int currentYear = visibleDates[visibleDates.length ~/ + (enableMultiView != null && enableMultiView ? 4 : 2)] + .year; + final int minYear = minDate.year; + + final int offset = getOffset(view); + if (((currentYear ~/ offset) * offset) - offset < + ((minYear ~/ offset) * offset)) { + return false; + } + } + } + + return true; + } + + /// Return the year view offset based on picker view(year, decade, century). + static int getOffset(dynamic view) { + final DateRangePickerView pickerView = getPickerView(view); + switch (pickerView) { + case DateRangePickerView.month: + break; + case DateRangePickerView.year: + return 1; + case DateRangePickerView.decade: + return 10; + case DateRangePickerView.century: + return 100; + } + return 0; + } + + /// Get the visible dates based on the date value and visible dates count. + static List getVisibleYearDates( + dynamic date, DateRangePickerView view, bool isHijri) { + final List datesCollection = []; + dynamic currentDate; + const int daysCount = 12; + switch (view) { + case DateRangePickerView.month: + break; + case DateRangePickerView.year: + { + for (int i = 1; i <= daysCount; i++) { + currentDate = getDate(date.year, i, 1, isHijri); + datesCollection.add(currentDate); + } + } + break; + case DateRangePickerView.decade: + { + final int year = (date.year ~/ 10) * 10; + + for (int i = 0; i < daysCount; i++) { + currentDate = getDate(year + i, 1, 1, isHijri); + datesCollection.add(currentDate); + } + } + break; + case DateRangePickerView.century: + { + final int year = (date.year ~/ 100) * 100; + for (int i = 0; i < daysCount; i++) { + currentDate = getDate(year + (i * 10), 1, 1, isHijri); + + datesCollection.add(currentDate); + } + } + } + + return datesCollection; + } + + /// Check the next view have enabled dates or not. + static bool canMoveToNextView( + DateRangePickerView view, + int numberOfWeeksInView, + dynamic maxDate, + List visibleDates, + bool enableMultiView, + bool isHijri) { + switch (view) { + case DateRangePickerView.month: + { + if (!isHijri && numberOfWeeksInView != 6) { + DateTime nextViewDate = visibleDates[visibleDates.length - 1]; + nextViewDate = addDuration(nextViewDate, const Duration(days: 1)); + if (!isSameOrBeforeDate(maxDate, nextViewDate)) { + return false; + } + } else { + final dynamic currentDate = visibleDates[visibleDates.length ~/ + (enableMultiView != null && enableMultiView ? 4 : 2)]; + final dynamic nextDate = getNextMonthDate(currentDate); + if (((nextDate.month > maxDate.month && + nextDate.year == maxDate.year) || + nextDate.year > maxDate.year)) { + return false; + } + } + } + break; + case DateRangePickerView.year: + case DateRangePickerView.decade: + case DateRangePickerView.century: + { + final int currentYear = visibleDates[visibleDates.length ~/ + (enableMultiView != null && enableMultiView ? 4 : 2)] + .year; + final int maxYear = maxDate.year; + final int offset = getOffset(view); + if (((currentYear ~/ offset) * offset) + offset > + ((maxYear ~/ offset) * offset)) { + return false; + } + } + } + return true; + } + + /// Return the copy of the list. + static List cloneList(List value) { + if (value == null || value.isEmpty) { + return null; + } + + return value.sublist(0); + } + + /// Determine the current platform is mobile platform(android or iOS). + static bool isMobileLayout(TargetPlatform platform) { + if (kIsWeb) { + return false; + } + + return platform == TargetPlatform.android || platform == TargetPlatform.iOS; + } + + /// 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) { + if (date.month == 1) { + if (format == 'M' || format == 'MM') { + return date.month.toString(); + } else if (format == 'MMM') { + return localizations.shortMuharramLabel; + } + return localizations.muharramLabel; + } else if (date.month == 2) { + if (format == 'M' || format == 'MM') { + return date.month.toString(); + } else if (format == 'MMM') { + return localizations.shortSafarLabel; + } + return localizations.safarLabel; + } else if (date.month == 3) { + if (format == 'M' || format == 'MM') { + return date.month.toString(); + } else if (format == 'MMM') { + return localizations.shortRabi1Label; + } + return localizations.rabi1Label; + } else if (date.month == 4) { + if (format == 'M' || format == 'MM') { + return date.month.toString(); + } else if (format == 'MMM') { + return localizations.shortRabi2Label; + } + return localizations.rabi2Label; + } else if (date.month == 5) { + if (format == 'M' || format == 'MM') { + return date.month.toString(); + } else if (format == 'MMM') { + return localizations.shortJumada1Label; + } + return localizations.jumada1Label; + } else if (date.month == 6) { + if (format == 'M' || format == 'MM') { + return date.month.toString(); + } else if (format == 'MMM') { + return localizations.shortJumada2Label; + } + return localizations.jumada2Label; + } else if (date.month == 7) { + if (format == 'M' || format == 'MM') { + return date.month.toString(); + } else if (format == 'MMM') { + return localizations.shortRajabLabel; + } + return localizations.rajabLabel; + } else if (date.month == 8) { + if (format == 'M' || format == 'MM') { + return date.month.toString(); + } else if (format == 'MMM') { + return localizations.shortShaabanLabel; + } + + return localizations.shaabanLabel; + } else if (date.month == 9) { + if (format == 'M' || format == 'MM') { + return date.month.toString(); + } else if (format == 'MMM') { + return localizations.shortRamadanLabel; + } + + return localizations.ramadanLabel; + } else if (date.month == 10) { + if (format == 'M' || format == 'MM') { + return date.month.toString(); + } else if (format == 'MMM') { + return localizations.shortShawwalLabel; + } + return localizations.shawwalLabel; + } else if (date.month == 11) { + if (format == 'M' || format == 'MM') { + return date.month.toString(); + } else if (format == 'MMM') { + return localizations.shortDhualqiLabel; + } + return localizations.dhualqiLabel; + } else { + if (format == 'M' || format == 'MM') { + return date.month.toString(); + } else if (format == 'MMM') { + return localizations.shortDhualhiLabel; + } + return localizations.dhualhiLabel; + } + } + + /// Returns teh [DateRangePickerView] value based on the given value. + static DateRangePickerView getPickerView(dynamic view) { + if (view is DateRangePickerView) { + return view; + } + + switch (view) { + case HijriDatePickerView.month: + { + return DateRangePickerView.month; + } + case HijriDatePickerView.year: + { + return DateRangePickerView.year; + } + case HijriDatePickerView.decade: + { + return DateRangePickerView.decade; + } + } + + return null; + } + + /// Returns teh [HijriDatePickerView] value based on the given value. + static HijriDatePickerView getHijriPickerView(dynamic view) { + if (view is HijriDatePickerView) { + return view; + } + + switch (view) { + case DateRangePickerView.month: + { + return HijriDatePickerView.month; + } + case DateRangePickerView.year: + { + return HijriDatePickerView.year; + } + case DateRangePickerView.decade: + { + return HijriDatePickerView.decade; + } + case DateRangePickerView.century: + { + return HijriDatePickerView.decade; + } + } + + return null; + } + + /// Returns the number of weeks in view for the picker. + static int getNumberOfWeeksInView(dynamic monthViewSettings, bool isHijri) { + if (isHijri) { + return 6; + } + + return monthViewSettings.numberOfWeeksInView; + } + + /// Determines whether the leading and trailing dates can be shown or not. + static bool canShowLeadingAndTrailingDates( + dynamic monthViewSettings, bool isHijri) { + if (isHijri) { + return false; + } + + return monthViewSettings.showTrailingAndLeadingDates; + } + + /// Returns the today date value. + static dynamic getToday(bool isHijri) { + if (isHijri) { + return HijriDateTime.now(); + } + + return DateTime.now(); + } + + /// Returns the required date with the given parameter values. + static dynamic getDate(int year, int month, int day, bool isHijri) { + if (isHijri) { + return HijriDateTime(year, month, day); + } + + return DateTime(year, month, day); + } + + /// Check both the dates are placed on same year, decade, century cell. + /// Eg., In year view, 20-01-2020 and 21-01-2020 dates are not equal but + /// both the dates are placed in same year cell. + /// Note: This method not applicable for month view. + static bool isSameCellDates(dynamic date, dynamic currentDate, dynamic view) { + if (date == null || currentDate == null) { + return false; + } + + final DateRangePickerView pickerView = getPickerView(view); + if (pickerView == DateRangePickerView.month) { + return false; + } + + if (pickerView == DateRangePickerView.year) { + return date.month == currentDate.month && date.year == currentDate.year; + } else if (pickerView == DateRangePickerView.decade) { + return date.year == currentDate.year; + } else if (pickerView == DateRangePickerView.century) { + return date.year ~/ 10 == currentDate.year ~/ 10; + } + + return false; + } + + /// Check the year cell index date as leading date or not. + /// Eg., Decade view holds 12 cells(2020 - 2031) and it have leading decade + /// view dates(2030 and 2031). The below method used to identify the date + /// as leading date or not. + /// Note: This method not applicable for month view. + static bool isLeadingCellDate( + int index, int viewStartIndex, List visibleDates, dynamic view) { + final DateRangePickerView pickerView = getPickerView(view); + if (pickerView == DateRangePickerView.month || + pickerView == DateRangePickerView.year) { + return false; + } + + final dynamic currentDate = visibleDates[index]; + final dynamic viewStartDate = visibleDates[viewStartIndex]; + + if (pickerView == DateRangePickerView.decade) { + return currentDate.year ~/ 10 != viewStartDate.year ~/ 10; + } else if (pickerView == DateRangePickerView.century) { + return currentDate.year ~/ 100 != viewStartDate.year ~/ 100; + } + + return false; + } + + /// Check the date is enabled or not based on min and max date value. + /// If picker max date as 20-12-2020 and selected date value as 21-12-2020 + /// 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) { + if (date == null || minDate == null || maxDate == null) { + return true; + } + + final DateRangePickerView pickerView = getPickerView(view); + if (pickerView == DateRangePickerView.month) { + return false; + } + + final dynamic today = getToday(isHijri); + if (pickerView == DateRangePickerView.year) { + return ((date.month >= minDate.month && date.year == minDate.year) || + date.year > minDate.year) && + ((date.month <= maxDate.month && date.year == maxDate.year) || + date.year < maxDate.year) && + (enablePastDates || + (!enablePastDates && + ((date.month >= today.month && date.year == today.year) || + date.year > today.year))); + } else if (pickerView == DateRangePickerView.decade) { + return date.year >= minDate.year && + date.year <= maxDate.year && + (enablePastDates || (!enablePastDates && date.year >= today.year)); + } else if (pickerView == DateRangePickerView.century) { + final int currentYear = date.year ~/ 10; + return currentYear >= (minDate.year ~/ 10) && + currentYear <= (maxDate.year ~/ 10) && + (enablePastDates || + (!enablePastDates && currentYear >= today.year ~/ 10)); + } + + return false; + } + + /// Return the last date of the month, year and decade based on view. + /// Eg., If picker view is year and the date value as 20-01-2020 then + /// it return the last date of the month(31-01-2020). + /// Note: This method not applicable for month view. + static dynamic getLastDate(dynamic date, dynamic view, bool isHijri) { + final DateRangePickerView pickerView = getPickerView(view); + if (pickerView == DateRangePickerView.month) { + return date; + } + + if (pickerView == DateRangePickerView.year) { + final dynamic currentDate = + getDate(date.year, date.month + 1, 1, isHijri); + return subtractDuration(currentDate, const Duration(days: 1)); + } else if (pickerView == DateRangePickerView.decade) { + final dynamic currentDate = getDate(date.year + 1, 1, 1, isHijri); + return subtractDuration(currentDate, const Duration(days: 1)); + } else if (pickerView == DateRangePickerView.century) { + final dynamic currentDate = + getDate(((date.year ~/ 10) * 10) + 10, 1, 1, isHijri); + return subtractDuration(currentDate, const Duration(days: 1)); + } + + return date; + } + + /// 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}) { + if (date == null) { + return -1; + } + + final DateRangePickerView pickerView = getPickerView(view); + viewStartIndex = viewStartIndex == -1 ? 0 : viewStartIndex; + viewEndIndex = viewEndIndex == -1 ? dates.length - 1 : viewEndIndex; + for (int i = viewStartIndex; i <= viewEndIndex; i++) { + final dynamic currentDate = dates[i]; + if (isSameCellDates(date, currentDate, pickerView)) { + return i; + } + } + + return -1; + } +} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_view.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_view.dart deleted file mode 100644 index 8782f6aa8..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_view.dart +++ /dev/null @@ -1,1515 +0,0 @@ -part of datepicker; - -@immutable -class _PickerView extends StatefulWidget { - const _PickerView(this.picker, this.controller, this.visibleDates, this.width, - this.height, this.datePickerTheme, this.focusNode, this.textScaleFactor, - {Key key, - this.getPickerStateDetails, - this.updatePickerStateDetails, - this.isRtl}) - : super(key: key); - - final List visibleDates; - final SfDateRangePicker picker; - final DateRangePickerController controller; - final double width; - final double height; - final _UpdatePickerState getPickerStateDetails; - final _UpdatePickerState updatePickerStateDetails; - final SfDateRangePickerThemeData datePickerTheme; - final bool isRtl; - final FocusNode focusNode; - final double textScaleFactor; - - @override - _PickerViewState createState() => _PickerViewState(); -} - -class _PickerViewState extends State<_PickerView> - with TickerProviderStateMixin { - _PickerStateArgs _pickerStateDetails; - _IMonthViewPainter _monthView; - _IPickerYearView _yearView; - Offset _mouseHoverPosition; - - /// The date time property used to range selection to store the - /// previous selected date value in range. - DateTime _previousSelectedDate; - - //// drag start boolean variable used to identify whether the drag started or not - //// For example., if user start drag from disabled date then the start date of the range not created - //// so in drag update method update the end date of existing selected range. - bool _isDragStart; - - @override - void initState() { - _pickerStateDetails = _PickerStateArgs(); - _isDragStart = false; - super.initState(); - } - - @override - Widget build(BuildContext context) { - final Locale locale = Localizations.localeOf(context); - widget.getPickerStateDetails(_pickerStateDetails); - - switch (widget.controller.view) { - case DateRangePickerView.month: - { - return GestureDetector( - child: MouseRegion( - onEnter: _pointerEnterEvent, - onHover: _pointerHoverEvent, - onExit: _pointerExitEvent, - child: _addMonthView(locale, widget.datePickerTheme), - ), - onTapUp: _updateTapCallback, - onHorizontalDragStart: _getDragStartCallback(), - onVerticalDragStart: _getDragStartCallback(), - onHorizontalDragUpdate: _getDragUpdateCallback(), - onVerticalDragUpdate: _getDragUpdateCallback(), - ); - } - case DateRangePickerView.year: - case DateRangePickerView.decade: - case DateRangePickerView.century: - { - return GestureDetector( - child: MouseRegion( - onEnter: _pointerEnterEvent, - onHover: _pointerHoverEvent, - onExit: _pointerExitEvent, - child: _addYearView(locale), - ), - onTapUp: _updateTapCallback, - onHorizontalDragStart: _getDragStartCallback(), - onVerticalDragStart: _getDragStartCallback(), - onHorizontalDragUpdate: _getDragUpdateCallback(), - onVerticalDragUpdate: _getDragUpdateCallback(), - ); - } - } - - return null; - } - - // Returns the month view as a child for the calendar view. - Widget _addMonthView( - Locale locale, SfDateRangePickerThemeData datePickerTheme) { - double viewHeaderHeight = widget.picker.monthViewSettings.viewHeaderHeight; - if (widget.controller.view == DateRangePickerView.month && - widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical) { - viewHeaderHeight = 0; - } - - final double height = widget.height - viewHeaderHeight; - _monthView = _getMonthView(locale, widget.datePickerTheme); - return Stack( - children: [ - _getViewHeader(viewHeaderHeight, locale, datePickerTheme), - Positioned( - left: 0, - top: viewHeaderHeight, - right: 0, - height: height, - child: RepaintBoundary( - child: CustomPaint( - painter: _monthView, - size: Size(widget.width, height), - ), - ), - ), - ], - ); - } - - _IMonthViewPainter _getMonthView( - Locale locale, SfDateRangePickerThemeData datePickerTheme) { - switch (widget.picker.selectionMode) { - case DateRangePickerSelectionMode.single: - { - return _MonthViewSingleSelectionPainter( - widget.visibleDates, - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.monthCellStyle, - widget.picker.selectionTextStyle, - widget.picker.rangeTextStyle, - widget.picker.selectionColor, - widget.picker.startRangeSelectionColor, - widget.picker.endRangeSelectionColor, - widget.picker.rangeSelectionColor, - widget.datePickerTheme, - widget.isRtl, - widget.picker.todayHighlightColor, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.picker.monthViewSettings.showTrailingAndLeadingDates, - widget.picker.monthViewSettings.blackoutDates, - widget.picker.monthViewSettings.specialDates, - widget.picker.monthViewSettings.weekendDays, - _pickerStateDetails._selectedDate, - widget.picker.selectionShape, - widget.picker.selectionRadius, - _mouseHoverPosition, - widget.picker.enableMultiView, - widget.picker.viewSpacing, - ValueNotifier(false), - widget.textScaleFactor); - } - case DateRangePickerSelectionMode.multiple: - { - return _MonthViewMultiSelectionPainter( - widget.visibleDates, - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.monthCellStyle, - widget.picker.selectionTextStyle, - widget.picker.rangeTextStyle, - widget.picker.selectionColor, - widget.picker.startRangeSelectionColor, - widget.picker.endRangeSelectionColor, - widget.picker.rangeSelectionColor, - widget.datePickerTheme, - widget.isRtl, - widget.picker.todayHighlightColor, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.picker.monthViewSettings.showTrailingAndLeadingDates, - widget.picker.monthViewSettings.blackoutDates, - widget.picker.monthViewSettings.specialDates, - widget.picker.monthViewSettings.weekendDays, - _cloneList(_pickerStateDetails._selectedDates), - widget.picker.selectionShape, - widget.picker.selectionRadius, - _mouseHoverPosition, - widget.picker.enableMultiView, - widget.picker.viewSpacing, - ValueNotifier(false), - widget.textScaleFactor); - } - case DateRangePickerSelectionMode.range: - { - return _MonthViewRangeSelectionPainter( - widget.visibleDates, - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.monthCellStyle, - widget.picker.selectionTextStyle, - widget.picker.rangeTextStyle, - widget.picker.selectionColor, - widget.picker.startRangeSelectionColor, - widget.picker.endRangeSelectionColor, - widget.picker.rangeSelectionColor, - widget.datePickerTheme, - widget.isRtl, - widget.picker.todayHighlightColor, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.picker.monthViewSettings.showTrailingAndLeadingDates, - widget.picker.monthViewSettings.blackoutDates, - widget.picker.monthViewSettings.specialDates, - widget.picker.monthViewSettings.weekendDays, - _pickerStateDetails._selectedRange, - widget.picker.selectionShape, - widget.picker.selectionRadius, - _mouseHoverPosition, - widget.picker.enableMultiView, - widget.picker.viewSpacing, - ValueNotifier(false), - widget.textScaleFactor); - } - case DateRangePickerSelectionMode.multiRange: - { - return _MonthViewMultiRangeSelectionPainter( - widget.visibleDates, - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.monthCellStyle, - widget.picker.selectionTextStyle, - widget.picker.rangeTextStyle, - widget.picker.selectionColor, - widget.picker.startRangeSelectionColor, - widget.picker.endRangeSelectionColor, - widget.picker.rangeSelectionColor, - widget.datePickerTheme, - widget.isRtl, - widget.picker.todayHighlightColor, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.picker.monthViewSettings.showTrailingAndLeadingDates, - widget.picker.monthViewSettings.blackoutDates, - widget.picker.monthViewSettings.specialDates, - widget.picker.monthViewSettings.weekendDays, - _cloneList(_pickerStateDetails._selectedRanges), - widget.picker.selectionShape, - widget.picker.selectionRadius, - _mouseHoverPosition, - widget.picker.enableMultiView, - widget.picker.viewSpacing, - ValueNotifier(false), - widget.textScaleFactor); - } - } - - return null; - } - - Widget _getViewHeader(double viewHeaderHeight, Locale locale, - SfDateRangePickerThemeData datePickerTheme) { - if (viewHeaderHeight == 0) { - return Positioned( - left: 0, - top: 0, - right: 0, - height: viewHeaderHeight, - child: Container()); - } - - final Color todayTextColor = - widget.picker.monthCellStyle.todayTextStyle != null && - widget.picker.monthCellStyle.todayTextStyle.color != null - ? widget.picker.monthCellStyle.todayTextStyle.color - : (widget.picker.todayHighlightColor != null && - widget.picker.todayHighlightColor != Colors.transparent - ? widget.picker.todayHighlightColor - : widget.datePickerTheme.todayHighlightColor); - - return Positioned( - left: 0, - top: 0, - right: 0, - height: viewHeaderHeight, - child: Container( - color: - widget.picker.monthViewSettings.viewHeaderStyle.backgroundColor ?? - widget.datePickerTheme.viewHeaderBackgroundColor, - child: RepaintBoundary( - child: CustomPaint( - painter: _PickerViewHeaderPainter( - widget.visibleDates, - widget.picker.monthViewSettings.viewHeaderStyle, - viewHeaderHeight, - widget.picker.monthViewSettings, - widget.datePickerTheme, - locale, - widget.isRtl, - widget.picker.monthCellStyle, - widget.picker.enableMultiView, - widget.picker.viewSpacing, - todayTextColor, - widget.textScaleFactor), - ), - ), - ), - ); - } - - void _updateTapCallback(TapUpDetails details) { - switch (widget.controller.view) { - case DateRangePickerView.month: - { - double viewHeaderHeight = - widget.picker.monthViewSettings.viewHeaderHeight; - if (widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical) { - viewHeaderHeight = 0; - } - - if (details.localPosition.dy < viewHeaderHeight) { - return; - } - - if (details.localPosition.dy > viewHeaderHeight) { - _handleTouch( - Offset(details.localPosition.dx, - details.localPosition.dy - viewHeaderHeight), - details); - } - } - break; - case DateRangePickerView.year: - case DateRangePickerView.decade: - case DateRangePickerView.century: - { - _handleYearPanelSelection( - Offset(details.localPosition.dx, details.localPosition.dy)); - } - } - - if (!widget.focusNode.hasFocus) { - widget.focusNode.requestFocus(); - } - } - - void _updateMouseHover(Offset globalPosition) { - final RenderBox box = context.findRenderObject(); - final Offset localPosition = box.globalToLocal(globalPosition); - final double viewHeaderHeight = - widget.controller.view == DateRangePickerView.month - ? widget.picker.monthViewSettings.viewHeaderHeight - : 0; - final double xPosition = localPosition.dx; - final double yPosition = localPosition.dy - viewHeaderHeight; - - if (localPosition.dy < viewHeaderHeight) { - return; - } - - setState(() { - _mouseHoverPosition = Offset(xPosition, yPosition); - }); - } - - void _pointerEnterEvent(PointerEnterEvent event) { - _updateMouseHover(event.position); - } - - void _pointerHoverEvent(PointerHoverEvent event) { - _updateMouseHover(event.position); - } - - void _pointerExitEvent(PointerExitEvent event) { - setState(() { - _mouseHoverPosition = null; - }); - } - - Widget _addYearView(Locale locale) { - _yearView = _getYearView(locale); - return RepaintBoundary( - child: CustomPaint( - painter: _yearView, - size: Size(widget.width, widget.height), - ), - ); - } - - _IPickerYearView _getYearView(Locale locale) { - switch (widget.controller.view) { - case DateRangePickerView.year: - { - return _PickerYearViewPainter( - 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.picker.enableMultiView, - widget.picker.viewSpacing, - widget.picker.selectionTextStyle, - widget.picker.rangeTextStyle, - widget.picker.selectionColor, - widget.picker.startRangeSelectionColor, - widget.picker.endRangeSelectionColor, - widget.picker.rangeSelectionColor, - !widget.picker.allowViewNavigation - ? _getSelectedValue( - widget.picker.selectionMode, _pickerStateDetails) - : null, - widget.picker.selectionMode, - widget.picker.selectionRadius, - ValueNotifier(false), - widget.textScaleFactor); - } - case DateRangePickerView.decade: - { - return _PickerDecadeViewPainter( - widget.visibleDates, - widget.picker.yearCellStyle, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.picker.todayHighlightColor, - widget.picker.selectionShape, - widget.isRtl, - widget.datePickerTheme, - locale, - _mouseHoverPosition, - widget.picker.enableMultiView, - widget.picker.viewSpacing, - widget.picker.selectionTextStyle, - widget.picker.rangeTextStyle, - widget.picker.selectionColor, - widget.picker.startRangeSelectionColor, - widget.picker.endRangeSelectionColor, - widget.picker.rangeSelectionColor, - !widget.picker.allowViewNavigation - ? _getSelectedValue( - widget.picker.selectionMode, _pickerStateDetails) - : null, - widget.picker.selectionMode, - widget.picker.selectionRadius, - ValueNotifier(false), - widget.textScaleFactor); - } - case DateRangePickerView.century: - { - return _PickerCenturyViewPainter( - widget.visibleDates, - widget.picker.yearCellStyle, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.picker.todayHighlightColor, - widget.picker.selectionShape, - widget.isRtl, - widget.datePickerTheme, - locale, - _mouseHoverPosition, - widget.picker.enableMultiView, - widget.picker.viewSpacing, - widget.picker.selectionTextStyle, - widget.picker.rangeTextStyle, - widget.picker.selectionColor, - widget.picker.startRangeSelectionColor, - widget.picker.endRangeSelectionColor, - widget.picker.rangeSelectionColor, - !widget.picker.allowViewNavigation - ? _getSelectedValue( - widget.picker.selectionMode, _pickerStateDetails) - : null, - widget.picker.selectionMode, - widget.picker.selectionRadius, - ValueNotifier(false), - widget.textScaleFactor); - } - case DateRangePickerView.month: - { - return null; - } - } - - return null; - } - - GestureDragStartCallback _getDragStartCallback() { - //// return drag start start event when selection mode as range or multi range. - if ((widget.controller.view != DateRangePickerView.month && - widget.picker.allowViewNavigation) || - !widget.picker.monthViewSettings.enableSwipeSelection) { - return null; - } - - if (widget.picker.selectionMode != DateRangePickerSelectionMode.range && - widget.picker.selectionMode != - DateRangePickerSelectionMode.multiRange) { - return null; - } - - switch (widget.controller.view) { - case DateRangePickerView.month: - { - return _dragStart; - } - case DateRangePickerView.year: - case DateRangePickerView.decade: - case DateRangePickerView.century: - return _dragStartOnYear; - } - - return null; - } - - GestureDragUpdateCallback _getDragUpdateCallback() { - //// return drag update start event when selection mode as range or multi range. - if ((widget.controller.view != DateRangePickerView.month && - widget.picker.allowViewNavigation) || - !widget.picker.monthViewSettings.enableSwipeSelection) { - return null; - } - - if (widget.picker.selectionMode != DateRangePickerSelectionMode.range && - widget.picker.selectionMode != - DateRangePickerSelectionMode.multiRange) { - return null; - } - - switch (widget.controller.view) { - case DateRangePickerView.month: - { - return _dragUpdate; - } - case DateRangePickerView.year: - case DateRangePickerView.decade: - case DateRangePickerView.century: - { - return _dragUpdateOnYear; - } - } - - return null; - } - - int _getYearViewIndex(double xPosition, double yPosition) { - int rowIndex, columnIndex; - int columnCount = _IPickerYearView._maxColumnCount; - double width = widget.width; - int index = -1; - if (widget.picker.enableMultiView) { - columnCount *= 2; - width -= widget.picker.viewSpacing; - if (xPosition > width / 2 && - xPosition < (width / 2) + widget.picker.viewSpacing) { - return index; - } else if (xPosition > width / 2) { - xPosition -= widget.picker.viewSpacing; - } - } - - final double cellWidth = width / columnCount; - final double cellHeight = widget.height / _IPickerYearView._maxRowCount; - if (yPosition < 0 || xPosition < 0) { - return index; - } - - rowIndex = xPosition ~/ cellWidth; - if (rowIndex >= columnCount) { - rowIndex = columnCount - 1; - } else if (rowIndex < 0) { - return index; - } - - if (widget.isRtl) { - rowIndex = _getRtlIndex(columnCount, rowIndex); - } - - columnIndex = yPosition ~/ cellHeight; - if (columnIndex >= _IPickerYearView._maxRowCount) { - columnIndex = _IPickerYearView._maxRowCount - 1; - } else if (columnIndex < 0) { - return index; - } - - const int totalDatesCount = - _IPickerYearView._maxRowCount * _IPickerYearView._maxColumnCount; - index = (columnIndex * _IPickerYearView._maxColumnCount) + - ((rowIndex ~/ _IPickerYearView._maxColumnCount) * totalDatesCount) + - (rowIndex % _IPickerYearView._maxColumnCount); - return widget.picker.enableMultiView && - _yearView._isTrailingDate( - index, (index ~/ totalDatesCount) * totalDatesCount) - ? -1 - : index; - } - - int _getSelectedIndex(double xPosition, double yPosition) { - int rowIndex, columnIndex; - double width = widget.width; - int index = -1; - int totalColumnCount = _kNumberOfDaysInWeek; - if (widget.picker.enableMultiView) { - width -= widget.picker.viewSpacing; - totalColumnCount *= 2; - if (xPosition > width / 2 && - xPosition < (width / 2) + widget.picker.viewSpacing) { - return index; - } else if (xPosition > width / 2) { - xPosition -= widget.picker.viewSpacing; - } - } - - if (yPosition < 0 || xPosition < 0) { - return index; - } - - double viewHeaderHeight = widget.picker.monthViewSettings.viewHeaderHeight; - if (widget.controller.view == DateRangePickerView.month && - widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical) { - viewHeaderHeight = 0; - } - - final double cellWidth = width / totalColumnCount; - final double cellHeight = (widget.height - viewHeaderHeight) / - widget.picker.monthViewSettings.numberOfWeeksInView; - rowIndex = (xPosition / cellWidth).truncate(); - if (rowIndex >= totalColumnCount) { - rowIndex = totalColumnCount - 1; - } else if (rowIndex < 0) { - return index; - } - - if (widget.isRtl) { - rowIndex = _getRtlIndex(totalColumnCount, rowIndex); - } - - columnIndex = (yPosition / cellHeight).truncate(); - if (columnIndex >= widget.picker.monthViewSettings.numberOfWeeksInView) { - columnIndex = widget.picker.monthViewSettings.numberOfWeeksInView - 1; - } else if (columnIndex < 0) { - return index; - } - - index = (columnIndex * _kNumberOfDaysInWeek) + - ((rowIndex ~/ _kNumberOfDaysInWeek) * - (widget.picker.monthViewSettings.numberOfWeeksInView * - _kNumberOfDaysInWeek)) + - (rowIndex % _kNumberOfDaysInWeek); - return index; - } - - void _dragStart(DragStartDetails details) { - //// Set drag start value as false, identifies the start date of the range not updated. - _isDragStart = false; - widget.getPickerStateDetails(_pickerStateDetails); - final double xPosition = details.localPosition.dx; - double yPosition = details.localPosition.dy; - if (widget.controller.view == DateRangePickerView.month && - widget.picker.navigationDirection == - DateRangePickerNavigationDirection.horizontal) { - yPosition = details.localPosition.dy - - widget.picker.monthViewSettings.viewHeaderHeight; - } - - final int index = _getSelectedIndex(xPosition, yPosition); - if (index == -1) { - return; - } - - final DateTime selectedDate = widget.visibleDates[index]; - if (!_isEnabledDate(widget.picker.minDate, widget.picker.maxDate, - widget.picker.enablePastDates, selectedDate)) { - return; - } - - final int currentMonthIndex = _getCurrentDateIndex(index); - if (!_isDateAsCurrentMonthDate( - widget.visibleDates[currentMonthIndex], - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.monthViewSettings.showTrailingAndLeadingDates, - selectedDate)) { - return; - } - - if (_isDateWithInVisibleDates(widget.visibleDates, - widget.picker.monthViewSettings.blackoutDates, selectedDate)) { - return; - } - - //// Set drag start value as false, identifies the start date of the range updated. - _isDragStart = true; - _updateSelectedRangesOnDragStart(_monthView, selectedDate); - - /// Assign start date of the range as previous selected date. - _previousSelectedDate = selectedDate; - - widget.updatePickerStateDetails(_pickerStateDetails); - } - - void _dragUpdate(DragUpdateDetails details) { - widget.getPickerStateDetails(_pickerStateDetails); - final double xPosition = details.localPosition.dx; - double yPosition = details.localPosition.dy; - if (widget.controller.view == DateRangePickerView.month && - widget.picker.navigationDirection == - DateRangePickerNavigationDirection.horizontal) { - yPosition = details.localPosition.dy - - widget.picker.monthViewSettings.viewHeaderHeight; - } - - final int index = _getSelectedIndex(xPosition, yPosition); - if (index == -1) { - return; - } - - final DateTime selectedDate = widget.visibleDates[index]; - if (!_isEnabledDate(widget.picker.minDate, widget.picker.maxDate, - widget.picker.enablePastDates, selectedDate)) { - return; - } - - final int currentMonthIndex = _getCurrentDateIndex(index); - if (!_isDateAsCurrentMonthDate( - widget.visibleDates[currentMonthIndex], - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.monthViewSettings.showTrailingAndLeadingDates, - selectedDate)) { - return; - } - - if (_isDateWithInVisibleDates(widget.visibleDates, - widget.picker.monthViewSettings.blackoutDates, selectedDate)) { - return; - } - - _updateSelectedRangesOnDragUpdateMonth(selectedDate); - - /// Assign start date of the range as previous selected date. - _previousSelectedDate = selectedDate; - - //// Set drag start value as false, identifies the start date of the range updated. - _isDragStart = true; - widget.updatePickerStateDetails(_pickerStateDetails); - } - - void _updateSelectedRangesOnDragStart(dynamic view, DateTime selectedDate) { - switch (widget.picker.selectionMode) { - case DateRangePickerSelectionMode.single: - case DateRangePickerSelectionMode.multiple: - break; - case DateRangePickerSelectionMode.range: - { - _pickerStateDetails._selectedRange = - PickerDateRange(selectedDate, null); - view._updateSelection(_pickerStateDetails); - } - break; - case DateRangePickerSelectionMode.multiRange: - { - _pickerStateDetails._selectedRanges ??= []; - _pickerStateDetails._selectedRanges - .add(PickerDateRange(selectedDate, null)); - _removeInterceptRanges( - _pickerStateDetails._selectedRanges, - _pickerStateDetails._selectedRanges[ - _pickerStateDetails._selectedRanges.length - 1]); - view._updateSelection(_pickerStateDetails); - } - } - } - - void _updateSelectedRangesOnDragUpdateMonth(DateTime selectedDate) { - switch (widget.picker.selectionMode) { - case DateRangePickerSelectionMode.single: - case DateRangePickerSelectionMode.multiple: - break; - case DateRangePickerSelectionMode.range: - { - //// Check the start date of the range updated or not, if not updated then create the new range. - if (!_isDragStart) { - _pickerStateDetails._selectedRange = - PickerDateRange(selectedDate, null); - } else { - if (_pickerStateDetails._selectedRange != null && - _pickerStateDetails._selectedRange.startDate != null) { - final PickerDateRange updatedRange = - _getSelectedRangeOnDragUpdate( - _pickerStateDetails._selectedRange, selectedDate); - if (_isRangeEquals( - _pickerStateDetails._selectedRange, updatedRange)) { - return; - } - - _pickerStateDetails._selectedRange = updatedRange; - } else { - _pickerStateDetails._selectedRange = - PickerDateRange(selectedDate, null); - } - } - _monthView._updateSelection(_pickerStateDetails); - } - break; - case DateRangePickerSelectionMode.multiRange: - { - _pickerStateDetails._selectedRanges ??= []; - final int count = _pickerStateDetails._selectedRanges.length; - PickerDateRange _lastRange; - if (count > 0) { - _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(PickerDateRange(selectedDate, null)); - } else { - if (_lastRange != null && _lastRange.startDate != null) { - final PickerDateRange updatedRange = - _getSelectedRangeOnDragUpdate(_lastRange, selectedDate); - if (_isRangeEquals(_lastRange, updatedRange)) { - return; - } - - _pickerStateDetails._selectedRanges[count - 1] = updatedRange; - } else { - _pickerStateDetails._selectedRanges - .add(PickerDateRange(selectedDate, null)); - } - } - - _removeInterceptRanges( - _pickerStateDetails._selectedRanges, - _pickerStateDetails._selectedRanges[ - _pickerStateDetails._selectedRanges.length - 1]); - _monthView._updateSelection(_pickerStateDetails); - } - } - } - - /// Return the range that start date is before of end date in month view. - PickerDateRange _getSelectedRangeOnDragUpdate( - PickerDateRange previousRange, DateTime selectedDate) { - final DateTime previousRangeStartDate = previousRange.startDate; - final DateTime previousRangeEndDate = - previousRange.endDate ?? previousRange.startDate; - DateTime rangeStartDate = previousRangeStartDate; - DateTime rangeEndDate = selectedDate; - if (isSameDate(previousRangeStartDate, _previousSelectedDate)) { - if (isSameOrBeforeDate(previousRangeEndDate, rangeEndDate)) { - rangeStartDate = selectedDate; - rangeEndDate = previousRangeEndDate; - } else { - rangeStartDate = previousRangeEndDate; - rangeEndDate = selectedDate; - } - } else if (isSameDate(previousRangeEndDate, _previousSelectedDate)) { - if (isSameOrAfterDate(previousRangeStartDate, rangeEndDate)) { - rangeStartDate = previousRangeStartDate; - rangeEndDate = selectedDate; - } else { - rangeStartDate = selectedDate; - rangeEndDate = previousRangeStartDate; - } - } - - return PickerDateRange(rangeStartDate, rangeEndDate); - } - - /// Return the range that start date is before of end date in year view. - PickerDateRange _getSelectedRangeOnDragUpdateYear( - PickerDateRange previousRange, DateTime selectedDate) { - final DateTime previousRangeStartDate = previousRange.startDate; - final DateTime previousRangeEndDate = - previousRange.endDate ?? previousRange.startDate; - DateTime rangeStartDate = previousRangeStartDate; - DateTime rangeEndDate = selectedDate; - if (_yearView._isSameView(previousRangeStartDate, _previousSelectedDate)) { - if (_yearView._isSameOrBeforeView(previousRangeEndDate, rangeEndDate)) { - rangeStartDate = selectedDate; - rangeEndDate = previousRangeEndDate; - } else { - rangeStartDate = previousRangeEndDate; - rangeEndDate = selectedDate; - } - } else if (_yearView._isSameView( - previousRangeEndDate, _previousSelectedDate)) { - if (_yearView._isSameOrAfterView(previousRangeStartDate, rangeEndDate)) { - rangeStartDate = previousRangeStartDate; - rangeEndDate = selectedDate; - } else { - rangeStartDate = selectedDate; - rangeEndDate = previousRangeStartDate; - } - } - - rangeEndDate = _getLastDate(_pickerStateDetails._view, rangeEndDate); - if (widget.picker.maxDate != null) { - rangeEndDate = rangeEndDate.isAfter(widget.picker.maxDate) - ? widget.picker.maxDate - : rangeEndDate; - } - rangeStartDate = _getFirstDate(_pickerStateDetails._view, rangeStartDate); - if (widget.picker.minDate != null) { - rangeStartDate = rangeStartDate.isBefore(widget.picker.minDate) - ? widget.picker.minDate - : rangeStartDate; - } - return PickerDateRange(rangeStartDate, rangeEndDate); - } - - void _updateSelectedRangesOnDragUpdateYear(DateTime selectedDate) { - switch (widget.picker.selectionMode) { - case DateRangePickerSelectionMode.single: - case DateRangePickerSelectionMode.multiple: - break; - case DateRangePickerSelectionMode.range: - { - //// Check the start date of the range updated or not, if not updated then create the new range. - if (!_isDragStart) { - _pickerStateDetails._selectedRange = - PickerDateRange(selectedDate, null); - } else { - if (_pickerStateDetails._selectedRange != null && - _pickerStateDetails._selectedRange.startDate != null) { - final PickerDateRange updatedRange = - _getSelectedRangeOnDragUpdateYear( - _pickerStateDetails._selectedRange, selectedDate); - if (_isRangeEquals( - _pickerStateDetails._selectedRange, updatedRange)) { - return; - } - - _pickerStateDetails._selectedRange = updatedRange; - } else { - _pickerStateDetails._selectedRange = - PickerDateRange(selectedDate, null); - } - } - _yearView._updateSelection(_pickerStateDetails); - } - break; - case DateRangePickerSelectionMode.multiRange: - { - _pickerStateDetails._selectedRanges ??= []; - final int count = _pickerStateDetails._selectedRanges.length; - PickerDateRange _lastRange; - if (count > 0) { - _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(PickerDateRange(selectedDate, null)); - } else { - if (_lastRange != null && _lastRange.startDate != null) { - final PickerDateRange updatedRange = - _getSelectedRangeOnDragUpdateYear(_lastRange, selectedDate); - if (_isRangeEquals(_lastRange, updatedRange)) { - return; - } - - _pickerStateDetails._selectedRanges[count - 1] = updatedRange; - } else { - _pickerStateDetails._selectedRanges - .add(PickerDateRange(selectedDate, null)); - } - } - - _removeInterceptRanges( - _pickerStateDetails._selectedRanges, - _pickerStateDetails._selectedRanges[ - _pickerStateDetails._selectedRanges.length - 1]); - _yearView._updateSelection(_pickerStateDetails); - } - } - } - - void _dragStartOnYear(DragStartDetails details) { - //// 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); - if (index == -1) { - return; - } - - final DateTime selectedDate = widget.visibleDates[index]; - if (!_yearView._isBetweenMinMaxMonth(selectedDate)) { - return; - } - - //// Set drag start value as false, identifies the start date of the range updated. - _isDragStart = true; - _updateSelectedRangesOnDragStart(_yearView, selectedDate); - _previousSelectedDate = selectedDate; - - widget.updatePickerStateDetails(_pickerStateDetails); - } - - void _dragUpdateOnYear(DragUpdateDetails details) { - widget.getPickerStateDetails(_pickerStateDetails); - final int index = - _getYearViewIndex(details.localPosition.dx, details.localPosition.dy); - if (index == -1) { - return; - } - - final DateTime selectedDate = widget.visibleDates[index]; - if (!_yearView._isBetweenMinMaxMonth(selectedDate)) { - return; - } - - _updateSelectedRangesOnDragUpdateYear(selectedDate); - _previousSelectedDate = selectedDate; - - //// Set drag start value as false, identifies the start date of the range updated. - _isDragStart = true; - widget.updatePickerStateDetails(_pickerStateDetails); - } - - void _handleTouch(Offset details, TapUpDetails tapUpDetails) { - widget.getPickerStateDetails(_pickerStateDetails); - if (widget.controller.view == DateRangePickerView.month) { - final int index = _getSelectedIndex(details.dx, details.dy); - if (index == -1) { - return; - } - - final DateTime selectedDate = widget.visibleDates[index]; - if (!_isEnabledDate(widget.picker.minDate, widget.picker.maxDate, - widget.picker.enablePastDates, selectedDate)) { - return; - } - - final int currentMonthIndex = _getCurrentDateIndex(index); - if (!_isDateAsCurrentMonthDate( - widget.visibleDates[currentMonthIndex], - widget.picker.monthViewSettings.numberOfWeeksInView, - widget.picker.monthViewSettings.showTrailingAndLeadingDates, - selectedDate)) { - return; - } - - if (_isDateWithInVisibleDates(widget.visibleDates, - widget.picker.monthViewSettings.blackoutDates, selectedDate)) { - return; - } - - _drawSelection(selectedDate); - widget.updatePickerStateDetails(_pickerStateDetails); - } - } - - int _getCurrentDateIndex(int index) { - final int datesCount = widget.picker.monthViewSettings.numberOfWeeksInView * - _kNumberOfDaysInWeek; - int currentMonthIndex = datesCount ~/ 2; - if (widget.picker.enableMultiView && index >= datesCount) { - currentMonthIndex += datesCount; - } - - return currentMonthIndex; - } - - void _drawSingleSelectionForYear(DateTime selectedDate) { - if (widget.picker.toggleDaySelection && - _yearView._isSameView( - selectedDate, _pickerStateDetails._selectedDate)) { - selectedDate = null; - } - - _pickerStateDetails._selectedDate = selectedDate; - } - - void _drawMultipleSelectionForYear(DateTime selectedDate) { - int selectedIndex = -1; - if (_pickerStateDetails._selectedDates != null && - _pickerStateDetails._selectedDates.isNotEmpty) { - selectedIndex = _getYearCellIndex( - _pickerStateDetails._selectedDates, selectedDate, _yearView); - } - - if (selectedIndex == -1) { - _pickerStateDetails._selectedDates ??= []; - _pickerStateDetails._selectedDates.add(selectedDate); - } else { - _pickerStateDetails._selectedDates.removeAt(selectedIndex); - } - } - - void _drawRangeSelectionForYear(DateTime selectedDate) { - if (_pickerStateDetails._selectedRange != null && - _pickerStateDetails._selectedRange.startDate != null && - (_pickerStateDetails._selectedRange.endDate == null || - _yearView._isSameView(_pickerStateDetails._selectedRange.startDate, - _pickerStateDetails._selectedRange.endDate))) { - DateTime startDate = _pickerStateDetails._selectedRange.startDate; - DateTime endDate = selectedDate; - if (startDate.isAfter(endDate)) { - final DateTime temp = startDate; - startDate = endDate; - endDate = temp; - } - - endDate = _getLastDate(_pickerStateDetails._view, endDate); - if (widget.picker.maxDate != null) { - endDate = endDate.isAfter(widget.picker.maxDate) - ? widget.picker.maxDate - : endDate; - } - - if (widget.picker.minDate != null) { - startDate = startDate.isBefore(widget.picker.minDate) - ? widget.picker.minDate - : startDate; - } - - _pickerStateDetails._selectedRange = PickerDateRange(startDate, endDate); - } else { - _pickerStateDetails._selectedRange = PickerDateRange(selectedDate, null); - } - } - - void _drawRangesSelectionForYear(DateTime selectedDate) { - _pickerStateDetails._selectedRanges ??= []; - int count = _pickerStateDetails._selectedRanges.length; - PickerDateRange _lastRange; - if (count > 0) { - _lastRange = _pickerStateDetails._selectedRanges[count - 1]; - } - - if (_lastRange != null && - _lastRange.startDate != null && - (_lastRange.endDate == null || - _yearView._isSameView(_lastRange.startDate, _lastRange.endDate))) { - DateTime startDate = _lastRange.startDate; - DateTime endDate = selectedDate; - if (startDate.isAfter(endDate)) { - final DateTime temp = startDate; - startDate = endDate; - endDate = temp; - } - - endDate = _getLastDate(_pickerStateDetails._view, endDate); - if (widget.picker.maxDate != null) { - endDate = endDate.isAfter(widget.picker.maxDate) - ? widget.picker.maxDate - : endDate; - } - - if (widget.picker.minDate != null) { - startDate = startDate.isBefore(widget.picker.minDate) - ? widget.picker.minDate - : startDate; - } - - final PickerDateRange newRange = PickerDateRange(startDate, endDate); - _pickerStateDetails._selectedRanges[count - 1] = newRange; - } else { - _pickerStateDetails._selectedRanges - .add(PickerDateRange(selectedDate, null)); - } - - count = _pickerStateDetails._selectedRanges.length; - _removeInterceptRanges( - _pickerStateDetails._selectedRanges, - _pickerStateDetails - ._selectedRanges[_pickerStateDetails._selectedRanges.length - 1]); - _lastRange = _pickerStateDetails - ._selectedRanges[_pickerStateDetails._selectedRanges.length - 1]; - if (count != _pickerStateDetails._selectedRanges.length && - (_lastRange.endDate == null || - _yearView._isSameView(_lastRange.endDate, _lastRange.startDate))) { - _pickerStateDetails._selectedRanges.removeLast(); - } - } - - void _drawYearSelection(DateTime selectedDate) { - switch (widget.picker.selectionMode) { - case DateRangePickerSelectionMode.single: - _drawSingleSelectionForYear(selectedDate); - break; - case DateRangePickerSelectionMode.multiple: - _drawMultipleSelectionForYear(selectedDate); - break; - case DateRangePickerSelectionMode.range: - _drawRangeSelectionForYear(selectedDate); - break; - case DateRangePickerSelectionMode.multiRange: - _drawRangesSelectionForYear(selectedDate); - } - - _yearView._updateSelection(_pickerStateDetails); - } - - void _handleYearPanelSelection(Offset details) { - final int _selectedIndex = _getYearViewIndex(details.dx, details.dy); - final int viewCount = widget.picker.enableMultiView ? 2 : 1; - if (_selectedIndex == -1 || _selectedIndex >= 12 * viewCount) { - return; - } - - final DateTime date = widget.visibleDates[_selectedIndex]; - widget.getPickerStateDetails(_pickerStateDetails); - if (!widget.picker.allowViewNavigation) { - if (!_yearView._isBetweenMinMaxMonth(date)) { - return; - } - - _drawYearSelection(date); - widget.updatePickerStateDetails(_pickerStateDetails); - return; - } - - switch (widget.controller.view) { - case DateRangePickerView.month: - break; - case DateRangePickerView.century: - { - final int year = date.year ~/ 10; - final int minYear = widget.picker.minDate.year ~/ 10; - final int maxYear = widget.picker.maxDate.year ~/ 10; - if (year < minYear || year > maxYear) { - return; - } - - _pickerStateDetails._view = DateRangePickerView.decade; - } - break; - case DateRangePickerView.decade: - { - final int year = date.year; - if (year < widget.picker.minDate.year || - year > widget.picker.maxDate.year) { - return; - } - - _pickerStateDetails._view = DateRangePickerView.year; - } - break; - case DateRangePickerView.year: - { - final int year = date.year; - final int month = date.month; - final int minYear = widget.picker.minDate.year; - final int maxYear = widget.picker.maxDate.year; - if ((year < minYear || - (year == minYear && month < widget.picker.minDate.month)) || - (year > maxYear || - (year == maxYear && month > widget.picker.maxDate.month))) { - return; - } - - _pickerStateDetails._view = DateRangePickerView.month; - } - } - - _pickerStateDetails._currentDate = date; - widget.updatePickerStateDetails(_pickerStateDetails); - } - - void _drawSingleSelectionForMonth(DateTime selectedDate) { - if (widget.picker.toggleDaySelection && - isSameDate(selectedDate, _pickerStateDetails._selectedDate)) { - selectedDate = null; - } - - _pickerStateDetails._selectedDate = selectedDate; - _monthView._updateSelection(_pickerStateDetails); - } - - void _drawMultipleSelectionForMonth(DateTime selectedDate) { - final int selectedIndex = _isDateIndexInCollection( - _pickerStateDetails._selectedDates, selectedDate); - if (selectedIndex == -1) { - _pickerStateDetails._selectedDates ??= []; - _pickerStateDetails._selectedDates.add(selectedDate); - } else { - _pickerStateDetails._selectedDates.removeAt(selectedIndex); - } - - _monthView._updateSelection(_pickerStateDetails); - } - - void _drawRangeSelectionForMonth(DateTime selectedDate) { - if (_pickerStateDetails._selectedRange != null && - _pickerStateDetails._selectedRange.startDate != null && - (_pickerStateDetails._selectedRange.endDate == null || - isSameDate(_pickerStateDetails._selectedRange.startDate, - _pickerStateDetails._selectedRange.endDate))) { - DateTime startDate = _pickerStateDetails._selectedRange.startDate; - DateTime endDate = selectedDate; - if (startDate.isAfter(endDate)) { - final DateTime temp = startDate; - startDate = endDate; - endDate = temp; - } - - _pickerStateDetails._selectedRange = PickerDateRange(startDate, endDate); - } else { - _pickerStateDetails._selectedRange = PickerDateRange(selectedDate, null); - } - - _monthView._updateSelection(_pickerStateDetails); - } - - void _drawRangesSelectionForMonth(DateTime selectedDate) { - _pickerStateDetails._selectedRanges ??= []; - int count = _pickerStateDetails._selectedRanges.length; - PickerDateRange lastRange; - if (count > 0) { - lastRange = _pickerStateDetails._selectedRanges[count - 1]; - } - - if (lastRange != null && - lastRange.startDate != null && - (lastRange.endDate == null || - isSameDate(lastRange.startDate, lastRange.endDate))) { - DateTime startDate = lastRange.startDate; - DateTime endDate = selectedDate; - if (startDate.isAfter(endDate)) { - final DateTime temp = startDate; - startDate = endDate; - endDate = temp; - } - - final PickerDateRange _newRange = PickerDateRange(startDate, endDate); - _pickerStateDetails._selectedRanges[count - 1] = _newRange; - } else { - _pickerStateDetails._selectedRanges - .add(PickerDateRange(selectedDate, null)); - } - - count = _pickerStateDetails._selectedRanges.length; - _removeInterceptRanges( - _pickerStateDetails._selectedRanges, - _pickerStateDetails - ._selectedRanges[_pickerStateDetails._selectedRanges.length - 1]); - lastRange = _pickerStateDetails - ._selectedRanges[_pickerStateDetails._selectedRanges.length - 1]; - if (count != _pickerStateDetails._selectedRanges.length && - (lastRange.endDate == null || - isSameDate(lastRange.endDate, lastRange.startDate))) { - _pickerStateDetails._selectedRanges.removeLast(); - } - - _monthView._updateSelection(_pickerStateDetails); - } - - void _drawSelection(DateTime selectedDate) { - switch (widget.picker.selectionMode) { - case DateRangePickerSelectionMode.single: - _drawSingleSelectionForMonth(selectedDate); - break; - case DateRangePickerSelectionMode.multiple: - _drawMultipleSelectionForMonth(selectedDate); - break; - case DateRangePickerSelectionMode.range: - _drawRangeSelectionForMonth(selectedDate); - break; - case DateRangePickerSelectionMode.multiRange: - _drawRangesSelectionForMonth(selectedDate); - } - } - - int _removeInterceptRangesForMonth(PickerDateRange range, DateTime startDate, - DateTime endDate, int i, PickerDateRange selectedRangeValue) { - if (range != null && - !_isRangeEquals(range, selectedRangeValue) && - ((range.startDate != null && - ((startDate != null && - isSameDate(range.startDate, startDate)) || - (endDate != null && - isSameDate(range.startDate, endDate)))) || - (range.endDate != null && - ((startDate != null && isSameDate(range.endDate, startDate)) || - (endDate != null && isSameDate(range.endDate, endDate)))) || - (range.startDate != null && - range.endDate != null && - ((startDate != null && - isDateWithInDateRange( - range.startDate, range.endDate, startDate)) || - (endDate != null && - isDateWithInDateRange( - range.startDate, range.endDate, endDate)))) || - (startDate != null && - endDate != null && - ((range.startDate != null && - isDateWithInDateRange( - startDate, endDate, range.startDate)) || - (range.endDate != null && - isDateWithInDateRange( - startDate, endDate, range.endDate)))) || - (range.startDate != null && - range.endDate != null && - startDate != null && - endDate != null && - ((range.startDate.isAfter(startDate) && - range.endDate.isBefore(endDate)) || - (range.endDate.isAfter(startDate) && - range.startDate.isBefore(endDate)))))) { - return i; - } - - return null; - } - - int _removeInterceptRangesForYear(PickerDateRange range, DateTime startDate, - DateTime endDate, int i, PickerDateRange selectedRangeValue) { - if (range != null && - !_isRangeEquals(range, selectedRangeValue) && - ((range.startDate != null && - ((startDate != null && - _yearView._isSameView(range.startDate, startDate)) || - (endDate != null && - _yearView._isSameView(range.startDate, endDate)))) || - (range.endDate != null && - ((startDate != null && - _yearView._isSameView(range.endDate, startDate)) || - (endDate != null && - _yearView._isSameView(range.endDate, endDate)))) || - (range.startDate != null && - range.endDate != null && - ((startDate != null && - _isDateWithInYearRange(range.startDate, range.endDate, - startDate, _yearView)) || - (endDate != null && - _isDateWithInYearRange(range.startDate, range.endDate, - endDate, _yearView)))) || - (startDate != null && - endDate != null && - ((range.startDate != null && - _isDateWithInYearRange( - startDate, endDate, range.startDate, _yearView)) || - (range.endDate != null && - _isDateWithInYearRange( - startDate, endDate, range.endDate, _yearView)))) || - (range.startDate != null && - range.endDate != null && - startDate != null && - endDate != null && - ((range.startDate.isAfter(startDate) && - range.endDate.isBefore(endDate)) || - (range.endDate.isAfter(startDate) && - range.startDate.isBefore(endDate)))))) { - return i; - } - - return null; - } - - void _removeInterceptRanges(List selectedRanges, - PickerDateRange selectedRangeValue) { - if (selectedRanges == null || - selectedRanges.isEmpty || - selectedRangeValue == null) { - return; - } - - DateTime startDate = selectedRangeValue.startDate; - DateTime endDate = selectedRangeValue.endDate; - if (startDate != null && endDate != null && startDate.isAfter(endDate)) { - final DateTime temp = startDate; - startDate = endDate; - endDate = temp; - } - - final List interceptIndex = []; - for (int i = 0; i < selectedRanges.length; i++) { - final PickerDateRange range = selectedRanges[i]; - //// The below condition validate the following scenarios - //// Check the range as not null and range is not a new selected range, - //// Check the range start date as equal with selected range start or end date - //// Check the range end date as equal with selected range start or end date - //// Check the selected start date placed in between range start or end date - //// Check the selected end date placed in between range start or end date - //// Check the selected range occupies the range. - int index; - switch (_pickerStateDetails._view) { - case DateRangePickerView.month: - { - index = _removeInterceptRangesForMonth( - range, startDate, endDate, i, selectedRangeValue); - } - break; - case DateRangePickerView.year: - case DateRangePickerView.decade: - case DateRangePickerView.century: - { - index = _removeInterceptRangesForYear( - range, startDate, endDate, i, selectedRangeValue); - } - } - if (index != null) { - interceptIndex.add(index); - } - } - - interceptIndex.sort(); - for (int i = interceptIndex.length - 1; i >= 0; i--) { - selectedRanges.removeAt(interceptIndex[i]); - } - } -} 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 new file mode 100644 index 000000000..b598503f9 --- /dev/null +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view.dart @@ -0,0 +1,3324 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import '../../datepicker.dart'; +import 'date_picker_manager.dart'; +import 'picker_helper.dart'; + +/// Used to hold the year cell widgets. +class YearView extends StatefulWidget { + /// Constructor for create the year view widget used to hold the year cell + /// widgets. + 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); + + /// Defines the year cell style. + final dynamic cellStyle; + + /// Defines the text style for selected year cell. + final TextStyle selectionTextStyle; + + /// Defines the range text style for selected range year cell. + final TextStyle rangeTextStyle; + + /// Defines the background color for selected year cell. + final Color selectionColor; + + /// Defines the navigation direction for [SfDateRangePicker]. + final DateRangePickerNavigationDirection navigationDirection; + + /// Defines the background color for selected range start date year cell. + final Color startRangeSelectionColor; + + /// Defines the background color for selected range end date year cell. + final Color endRangeSelectionColor; + + /// Defines the background color for selected range in between dates cell. + final Color rangeSelectionColor; + + /// Holds the visible dates for the year view. + final List visibleDates; + + /// Used to identify the widget direction is RTL. + final bool isRtl; + + /// Defines the today cell highlight color. + final Color todayHighlightColor; + + /// The minimum date as much as the [SfDateRangePicker] will navigate. + final dynamic minDate; + + /// The maximum date as much as the [SfDateRangePicker] will navigate. + final dynamic maxDate; + + /// Decides to enable past dates or not. + final bool enablePastDates; + + /// Decides the year cell highlight and selection shape. + final DateRangePickerSelectionShape selectionShape; + + /// Holds the theme data for date range picker. + final SfDateRangePickerThemeData datePickerTheme; + + /// Used to specify the mouse hover position of the year view. + final ValueNotifier mouseHoverPosition; + + /// Used to call repaint when the selection changes. + final ValueNotifier selectionNotifier; + + /// Holds the selection radius of the year cell. + final double selectionRadius; + + /// Holds the [SfDateRangePicker] selection mode. + final DateRangePickerSelectionMode selectionMode; + + /// Decides to show the multi view of year view or not. + final bool enableMultiView; + + /// Specifies the space between the multi year views. + final double multiViewSpacing; + + /// Defines the text scale factor of [SfDateRangePicker]. + final double textScaleFactor; + + /// Defines the year view panel will draw selection or not. + final bool allowViewNavigation; + + /// Used to get the picker state details from picker view widget. + final UpdatePickerState getPickerStateDetails; + + /// Defines the current view of the picker. + final DateRangePickerView view; + + /// Used to build the widget that replaces the month cells in month view. + final DateRangePickerCellBuilder cellBuilder; + + /// Defines the month format for the year view cell text. + final String monthFormat; + + /// Defines the locale of the picker. + final Locale locale; + + /// Defines the width of the month view. + final double width; + + /// Defines the height of the month view. + final double height; + + /// Defines the pickerType for [SfDateRangePicker]. + final bool isHijri; + + /// Specifies the localizations. + final SfLocalizations localizations; + + /// Defines the year view maximum column count. + static const int maxColumnCount = 3; + + /// Defines the year view maximum row count. + static const int maxRowCount = 4; + + @override + _YearViewState createState() => _YearViewState(); +} + +class _YearViewState extends State { + PickerStateArgs _pickerStateDetails; + dynamic _selectedDate; + List _selectedDates; + dynamic _selectedRange; + List _selectedRanges; + List _children; + + @override + void initState() { + _pickerStateDetails = PickerStateArgs(); + widget.getPickerStateDetails(_pickerStateDetails); + _selectedDate = _pickerStateDetails.selectedDate; + _selectedDates = + DateRangePickerHelper.cloneList(_pickerStateDetails.selectedDates); + _selectedRange = _pickerStateDetails.selectedRange; + _selectedRanges = + DateRangePickerHelper.cloneList(_pickerStateDetails.selectedRanges); + widget.selectionNotifier.addListener(_updateSelection); + super.initState(); + } + + @override + void didUpdateWidget(YearView oldWidget) { + if (widget.height != oldWidget.height || + widget.width != oldWidget.width || + widget.enablePastDates != oldWidget.enablePastDates || + widget.minDate != oldWidget.minDate || + widget.view != oldWidget.view || + widget.maxDate != oldWidget.maxDate || + widget.cellBuilder != oldWidget.cellBuilder || + widget.selectionMode != oldWidget.selectionMode || + widget.multiViewSpacing != oldWidget.multiViewSpacing || + widget.enableMultiView != oldWidget.enableMultiView || + widget.allowViewNavigation != oldWidget.allowViewNavigation || + widget.navigationDirection != oldWidget.navigationDirection || + widget.visibleDates != oldWidget.visibleDates) { + _children.clear(); + } + + if (widget.selectionNotifier != oldWidget.selectionNotifier) { + oldWidget.selectionNotifier.removeListener(_updateSelection); + widget.selectionNotifier.addListener(_updateSelection); + } + + _updateSelection(isNeedSetState: false); + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + _children ??= []; + if (widget.cellBuilder != null && _children.isEmpty) { + double webUIPadding = 0; + double width = widget.width; + double height = widget.height; + int viewCount = 1; + final bool isHorizontalMultiView = widget.enableMultiView && + widget.navigationDirection == + DateRangePickerNavigationDirection.horizontal; + final bool isVerticalMultiView = widget.enableMultiView && + widget.navigationDirection == + DateRangePickerNavigationDirection.vertical; + + if (isHorizontalMultiView) { + webUIPadding = widget.multiViewSpacing; + viewCount = 2; + width = (width - webUIPadding) / viewCount; + } else if (isVerticalMultiView) { + webUIPadding = widget.multiViewSpacing; + viewCount = 2; + height = (height - webUIPadding) / viewCount; + } + + final double cellWidth = width / YearView.maxColumnCount; + final double cellHeight = height / YearView.maxRowCount; + final int visibleDatesCount = widget.visibleDates.length ~/ viewCount; + for (int j = 0; j < viewCount; j++) { + final int currentViewIndex = + widget.isRtl ? DateRangePickerHelper.getRtlIndex(viewCount, j) : j; + + final int viewStartIndex = j * visibleDatesCount; + + 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); + 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) + + (rowIndex * YearView.maxColumnCount); + } + + currentIndex += viewStartIndex; + if (xPosition >= viewEndPosition) { + xPosition = viewStartPosition; + yPosition += cellHeight; + } + + if ((widget.enableMultiView || widget.isHijri) && + DateRangePickerHelper.isLeadingCellDate(currentIndex, + viewStartIndex, widget.visibleDates, widget.view)) { + xPosition += cellWidth; + continue; + } + + final dynamic date = widget.visibleDates[currentIndex]; + + final DateRangePickerCellDetails cellDetails = + DateRangePickerCellDetails( + date: date, + visibleDates: widget.visibleDates, + bounds: Rect.fromLTWH( + xPosition, yPosition, cellWidth, cellHeight)); + final Widget child = widget.cellBuilder(context, cellDetails); + assert(child != null, 'Widget must not be null'); + _children.add(child); + xPosition += cellWidth; + } + } + } + + return _getRenderWidget(); + } + + MultiChildRenderObjectWidget _getRenderWidget() { + switch (widget.selectionMode) { + 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, + 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, + 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, + 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, + widgets: _children); + } + } + + return null; + } + + void _updateSelection({bool isNeedSetState = true}) { + widget.getPickerStateDetails(_pickerStateDetails); + if (widget.allowViewNavigation) { + _selectedDate = _pickerStateDetails.selectedDate; + _selectedDates = + DateRangePickerHelper.cloneList(_pickerStateDetails.selectedDates); + _selectedRange = _pickerStateDetails.selectedRange; + _selectedRanges = + DateRangePickerHelper.cloneList(_pickerStateDetails.selectedRanges); + return; + } + + if (_isSelectedValueEquals()) { + return; + } + + _children.clear(); + _selectedDate = _pickerStateDetails.selectedDate; + _selectedDates = + DateRangePickerHelper.cloneList(_pickerStateDetails.selectedDates); + _selectedRange = _pickerStateDetails.selectedRange; + _selectedRanges = + DateRangePickerHelper.cloneList(_pickerStateDetails.selectedRanges); + + if (!isNeedSetState) { + return; + } + + setState(() { + /// Update the state while selection notifier value and does not update + /// the state while did update widget call this method. + }); + } + + dynamic _getSelectedDateValue() { + switch (widget.selectionMode) { + case DateRangePickerSelectionMode.single: + { + return _selectedDate; + } + case DateRangePickerSelectionMode.multiple: + { + return _selectedDates; + } + case DateRangePickerSelectionMode.range: + { + return _selectedRange; + } + case DateRangePickerSelectionMode.multiRange: + { + return _selectedRanges; + } + } + + return null; + } + + bool _isSelectedValueEquals() { + switch (widget.selectionMode) { + case DateRangePickerSelectionMode.single: + { + return isSameDate(_selectedDate, _pickerStateDetails.selectedDate); + } + case DateRangePickerSelectionMode.multiple: + { + return DateRangePickerHelper.isDateCollectionEquals( + _selectedDates, _pickerStateDetails.selectedDates); + } + case DateRangePickerSelectionMode.range: + { + return DateRangePickerHelper.isRangeEquals( + _selectedRange, _pickerStateDetails.selectedRange); + } + case DateRangePickerSelectionMode.multiRange: + { + return DateRangePickerHelper.isDateRangesEquals( + _selectedRanges, _pickerStateDetails.selectedRanges); + } + } + return false; + } +} + +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, + {List widgets}) + : super(children: widgets); + + /// Defines the year cell style. + final dynamic cellStyle; + + final DateRangePickerNavigationDirection navigationDirection; + + /// Defines the text style for selected year cell. + final TextStyle selectionTextStyle; + + /// Defines the range text style for selected range year cell. + final TextStyle rangeTextStyle; + + /// Defines the background color for selected year cell. + final Color selectionColor; + + /// Defines the background color for selected range start date year cell. + final Color startRangeSelectionColor; + + /// Defines the background color for selected range end date year cell. + final Color endRangeSelectionColor; + + /// Defines the background color for selected range in between dates cell. + final Color rangeSelectionColor; + + /// Holds the visible dates for the year view. + final List visibleDates; + + /// Used to identify the widget direction is RTL. + final bool isRtl; + + /// Defines the today cell highlight color. + final Color todayHighlightColor; + + /// The minimum date as much as the [SfDateRangePicker] will navigate. + final dynamic minDate; + + /// The maximum date as much as the [SfDateRangePicker] will navigate. + final dynamic maxDate; + + /// Decides to enable past dates or not. + final bool enablePastDates; + + /// Decides the year cell highlight and selection shape. + final DateRangePickerSelectionShape selectionShape; + + /// Holds the theme data for date range picker. + final SfDateRangePickerThemeData datePickerTheme; + + /// Used to specify the mouse hover position of the year view. + final ValueNotifier mouseHoverPosition; + + /// Used to call repaint when the selection changes. + final ValueNotifier selectionNotifier; + + /// Holds the selected date value. + final dynamic selectedDate; + + /// Holds the selection radius of the year cell. + final double selectionRadius; + + /// Decides to show the multi view of year view or not. + final bool enableMultiView; + + /// Specifies the space between the multi year views. + final double multiViewSpacing; + + /// Defines the text scale factor of [SfDateRangePicker]. + final double textScaleFactor; + + final String monthFormat; + + final Locale locale; + + final double width; + + final double height; + + final DateRangePickerView view; + + /// Defines the pickerType for [SfDateRangePicker]. + final bool isHijri; + + /// Specifies the localizations. + final SfLocalizations localizations; + + @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); + } + + @override + void updateRenderObject( + BuildContext context, _SingleSelectionRenderObject renderObject) { + renderObject + ..visibleDates = visibleDates + ..cellStyle = cellStyle + ..minDate = minDate + ..maxDate = maxDate + ..enablePastDates = enablePastDates + ..todayHighlightColor = todayHighlightColor + ..selectionShape = selectionShape + ..isRtl = isRtl + ..datePickerTheme = datePickerTheme + ..mouseHoverPosition = mouseHoverPosition + ..enableMultiView = enableMultiView + ..multiViewSpacing = multiViewSpacing + ..selectionTextStyle = selectionTextStyle + ..rangeTextStyle = rangeTextStyle + ..selectionColor = selectionColor + ..startRangeSelectionColor = startRangeSelectionColor + ..endRangeSelectionColor = endRangeSelectionColor + ..rangeSelectionColor = rangeSelectionColor + ..selectedDate = selectedDate + ..selectionRadius = selectionRadius + ..textScaleFactor = textScaleFactor + ..width = width + ..height = height + ..isHijri = isHijri + ..localizations = localizations + ..navigationDirection = navigationDirection + ..monthFormat = monthFormat + ..locale = locale + ..view = view; + } +} + +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, + {List widgets}) + : super(children: widgets); + + /// Defines the year cell style. + final dynamic cellStyle; + + final DateRangePickerNavigationDirection navigationDirection; + + /// Defines the text style for selected year cell. + final TextStyle selectionTextStyle; + + /// Defines the range text style for selected range year cell. + final TextStyle rangeTextStyle; + + /// Defines the background color for selected year cell. + final Color selectionColor; + + /// Defines the background color for selected range start date year cell. + final Color startRangeSelectionColor; + + /// Defines the background color for selected range end date year cell. + final Color endRangeSelectionColor; + + /// Defines the background color for selected range in between dates cell. + final Color rangeSelectionColor; + + /// Holds the visible dates for the year view. + final List visibleDates; + + /// Used to identify the widget direction is RTL. + final bool isRtl; + + /// Defines the today cell highlight color. + final Color todayHighlightColor; + + /// The minimum date as much as the [SfDateRangePicker] will navigate. + final dynamic minDate; + + /// The maximum date as much as the [SfDateRangePicker] will navigate. + final dynamic maxDate; + + /// Decides to enable past dates or not. + final bool enablePastDates; + + /// Decides the year cell highlight and selection shape. + final DateRangePickerSelectionShape selectionShape; + + /// Holds the theme data for date range picker. + final SfDateRangePickerThemeData datePickerTheme; + + /// Used to specify the mouse hover position of the year view. + final ValueNotifier mouseHoverPosition; + + /// Used to call repaint when the selection changes. + final ValueNotifier selectionNotifier; + + /// Holds the selected dates value. + final List selectedDates; + + /// Holds the selection radius of the year cell. + final double selectionRadius; + + /// Decides to show the multi view of year view or not. + final bool enableMultiView; + + /// Specifies the space between the multi year views. + final double multiViewSpacing; + + /// Defines the text scale factor of [SfDateRangePicker]. + final double textScaleFactor; + + final String monthFormat; + + final Locale locale; + + final double width; + + final double height; + + final DateRangePickerView view; + + /// Defines the pickerType for [SfDateRangePicker]. + final bool isHijri; + + /// Specifies the localizations. + final SfLocalizations localizations; + + @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); + } + + @override + void updateRenderObject( + BuildContext context, _MultipleSelectionRenderObject renderObject) { + renderObject + ..visibleDates = visibleDates + ..cellStyle = cellStyle + ..minDate = minDate + ..maxDate = maxDate + ..enablePastDates = enablePastDates + ..todayHighlightColor = todayHighlightColor + ..selectionShape = selectionShape + ..isRtl = isRtl + ..datePickerTheme = datePickerTheme + ..mouseHoverPosition = mouseHoverPosition + ..enableMultiView = enableMultiView + ..multiViewSpacing = multiViewSpacing + ..selectionTextStyle = selectionTextStyle + ..rangeTextStyle = rangeTextStyle + ..selectionColor = selectionColor + ..startRangeSelectionColor = startRangeSelectionColor + ..endRangeSelectionColor = endRangeSelectionColor + ..rangeSelectionColor = rangeSelectionColor + ..selectedDates = selectedDates + ..selectionRadius = selectionRadius + ..textScaleFactor = textScaleFactor + ..width = width + ..height = height + ..isHijri = isHijri + ..localizations = localizations + ..navigationDirection = navigationDirection + ..monthFormat = monthFormat + ..locale = locale + ..view = view; + } +} + +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, + {List widgets}) + : super(children: widgets); + + /// Defines the year cell style. + final dynamic cellStyle; + + /// Defines the text style for selected year cell. + final TextStyle selectionTextStyle; + + /// Defines the range text style for selected range year cell. + final TextStyle rangeTextStyle; + + /// Defines the background color for selected year cell. + final Color selectionColor; + + /// Defines the background color for selected range start date year cell. + final Color startRangeSelectionColor; + + /// Defines the background color for selected range end date year cell. + final Color endRangeSelectionColor; + + /// Defines the background color for selected range in between dates cell. + final Color rangeSelectionColor; + + /// Holds the visible dates for the year view. + final List visibleDates; + + /// Used to identify the widget direction is RTL. + final bool isRtl; + + /// Defines the today cell highlight color. + final Color todayHighlightColor; + + /// The minimum date as much as the [SfDateRangePicker] will navigate. + final dynamic minDate; + + /// The maximum date as much as the [SfDateRangePicker] will navigate. + final dynamic maxDate; + + final DateRangePickerNavigationDirection navigationDirection; + + /// Decides to enable past dates or not. + final bool enablePastDates; + + /// Decides the year cell highlight and selection shape. + final DateRangePickerSelectionShape selectionShape; + + /// Holds the theme data for date range picker. + final SfDateRangePickerThemeData datePickerTheme; + + /// Used to specify the mouse hover position of the year view. + final ValueNotifier mouseHoverPosition; + + /// Used to call repaint when the selection changes. + final ValueNotifier selectionNotifier; + + /// Holds the selected range value.. + final dynamic selectedRange; + + /// Holds the selection radius of the year cell. + final double selectionRadius; + + /// Decides to show the multi view of year view or not. + final bool enableMultiView; + + /// Specifies the space between the multi year views. + final double multiViewSpacing; + + /// Defines the text scale factor of [SfDateRangePicker]. + final double textScaleFactor; + + final String monthFormat; + + final Locale locale; + + final double width; + + final double height; + + final DateRangePickerView view; + + /// Defines the pickerType for [SfDateRangePicker]. + final bool isHijri; + + /// Specifies the localizations. + final SfLocalizations localizations; + + @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); + } + + @override + void updateRenderObject( + BuildContext context, _RangeSelectionRenderObject renderObject) { + renderObject + ..visibleDates = visibleDates + ..cellStyle = cellStyle + ..minDate = minDate + ..maxDate = maxDate + ..enablePastDates = enablePastDates + ..todayHighlightColor = todayHighlightColor + ..selectionShape = selectionShape + ..isRtl = isRtl + ..datePickerTheme = datePickerTheme + ..mouseHoverPosition = mouseHoverPosition + ..enableMultiView = enableMultiView + ..multiViewSpacing = multiViewSpacing + ..selectionTextStyle = selectionTextStyle + ..rangeTextStyle = rangeTextStyle + ..selectionColor = selectionColor + ..startRangeSelectionColor = startRangeSelectionColor + ..endRangeSelectionColor = endRangeSelectionColor + ..rangeSelectionColor = rangeSelectionColor + ..selectedRange = selectedRange + ..selectionRadius = selectionRadius + ..textScaleFactor = textScaleFactor + ..width = width + ..height = height + ..isHijri = isHijri + ..localizations = localizations + ..navigationDirection = navigationDirection + ..monthFormat = monthFormat + ..locale = locale + ..view = view; + } +} + +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, + {List widgets}) + : super(children: widgets); + + /// Defines the year cell style. + final dynamic cellStyle; + + /// Defines the text style for selected year cell. + final TextStyle selectionTextStyle; + + /// Defines the range text style for selected range year cell. + final TextStyle rangeTextStyle; + + /// Defines the background color for selected year cell. + final Color selectionColor; + + /// Defines the background color for selected range start date year cell. + final Color startRangeSelectionColor; + + /// Defines the background color for selected range end date year cell. + final Color endRangeSelectionColor; + + /// Defines the background color for selected range in between dates cell. + final Color rangeSelectionColor; + + /// Holds the visible dates for the year view. + final List visibleDates; + + /// Used to identify the widget direction is RTL. + final bool isRtl; + + /// Defines the today cell highlight color. + final Color todayHighlightColor; + + /// The minimum date as much as the [SfDateRangePicker] will navigate. + final dynamic minDate; + + /// The maximum date as much as the [SfDateRangePicker] will navigate. + final dynamic maxDate; + + /// Defines the navigation direction for [SfDateRangePicker]. + final DateRangePickerNavigationDirection navigationDirection; + + /// Decides to enable past dates or not. + final bool enablePastDates; + + /// Decides the year cell highlight and selection shape. + final DateRangePickerSelectionShape selectionShape; + + /// Holds the theme data for date range picker. + final SfDateRangePickerThemeData datePickerTheme; + + /// Used to specify the mouse hover position of the year view. + final ValueNotifier mouseHoverPosition; + + /// Used to call repaint when the selection changes. + final ValueNotifier selectionNotifier; + + /// Holds the selected value based on [SfDateRangePicker] selection mode. + final List selectedRanges; + + /// Holds the selection radius of the year cell. + final double selectionRadius; + + /// Decides to show the multi view of year view or not. + final bool enableMultiView; + + /// Specifies the space between the multi year views. + final double multiViewSpacing; + + /// Defines the text scale factor of [SfDateRangePicker]. + final double textScaleFactor; + + final String monthFormat; + + final Locale locale; + + final double width; + + final double height; + + final DateRangePickerView view; + + /// Defines the pickerType for [SfDateRangePicker]. + final bool isHijri; + + /// Specifies the localizations. + final SfLocalizations localizations; + + @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); + } + + @override + void updateRenderObject( + BuildContext context, _MultiRangeSelectionRenderObject renderObject) { + renderObject + ..visibleDates = visibleDates + ..cellStyle = cellStyle + ..minDate = minDate + ..maxDate = maxDate + ..enablePastDates = enablePastDates + ..todayHighlightColor = todayHighlightColor + ..selectionShape = selectionShape + ..isRtl = isRtl + ..datePickerTheme = datePickerTheme + ..mouseHoverPosition = mouseHoverPosition + ..enableMultiView = enableMultiView + ..multiViewSpacing = multiViewSpacing + ..selectionTextStyle = selectionTextStyle + ..rangeTextStyle = rangeTextStyle + ..selectionColor = selectionColor + ..startRangeSelectionColor = startRangeSelectionColor + ..endRangeSelectionColor = endRangeSelectionColor + ..rangeSelectionColor = rangeSelectionColor + ..selectedRanges = selectedRanges + ..selectionRadius = selectionRadius + ..textScaleFactor = textScaleFactor + ..width = width + ..height = height + ..isHijri = isHijri + ..localizations = localizations + ..monthFormat = monthFormat + ..locale = locale + ..navigationDirection = navigationDirection + ..view = view; + } +} + +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); + + DateRangePickerNavigationDirection _navigationDirection; + + DateRangePickerNavigationDirection get navigationDirection => + _navigationDirection; + + set navigationDirection(DateRangePickerNavigationDirection value) { + if (_navigationDirection == value) { + return; + } + + _navigationDirection = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + dynamic _cellStyle; + + dynamic get cellStyle => _cellStyle; + + set cellStyle(dynamic value) { + if (_cellStyle == value) { + return; + } + + _cellStyle = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + TextStyle _selectionTextStyle; + + TextStyle get selectionTextStyle => _selectionTextStyle; + + set selectionTextStyle(TextStyle value) { + if (_selectionTextStyle == value) { + return; + } + + _selectionTextStyle = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + TextStyle _rangeTextStyle; + + TextStyle get rangeTextStyle => _rangeTextStyle; + + set rangeTextStyle(TextStyle value) { + if (_rangeTextStyle == value) { + return; + } + + _rangeTextStyle = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + Color _selectionColor; + + Color get selectionColor => _selectionColor; + + set selectionColor(Color value) { + if (_selectionColor == value) { + return; + } + + _selectionColor = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + Color _startRangeSelectionColor; + + Color get startRangeSelectionColor => _startRangeSelectionColor; + + set startRangeSelectionColor(Color value) { + if (_startRangeSelectionColor == value) { + return; + } + + _startRangeSelectionColor = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + Color _endRangeSelectionColor; + + Color get endRangeSelectionColor => _endRangeSelectionColor; + + set endRangeSelectionColor(Color value) { + if (_endRangeSelectionColor == value) { + return; + } + + _endRangeSelectionColor = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + Color _rangeSelectionColor; + + Color get rangeSelectionColor => _rangeSelectionColor; + + set rangeSelectionColor(Color value) { + if (_rangeSelectionColor == value) { + return; + } + + _rangeSelectionColor = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + List _visibleDates; + + List get visibleDates => _visibleDates; + + set visibleDates(List value) { + if (_visibleDates == value) { + return; + } + + _visibleDates = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + bool _isRtl; + + bool get isRtl => _isRtl; + + set isRtl(bool value) { + if (_isRtl == value) { + return; + } + + _isRtl = value; + markNeedsPaint(); + } + + Color _todayHighlightColor; + + Color get todayHighlightColor => _todayHighlightColor; + + set todayHighlightColor(Color value) { + if (_todayHighlightColor == value) { + return; + } + + _todayHighlightColor = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + SfDateRangePickerThemeData _datePickerTheme; + + SfDateRangePickerThemeData get datePickerTheme => _datePickerTheme; + + set datePickerTheme(SfDateRangePickerThemeData value) { + if (_datePickerTheme == value) { + return; + } + + _datePickerTheme = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + dynamic _minDate; + + dynamic get minDate => _minDate; + + set minDate(dynamic value) { + if (_minDate == value) { + return; + } + + _minDate = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + dynamic _maxDate; + + dynamic get maxDate => _maxDate; + + set maxDate(dynamic value) { + if (_maxDate == value) { + return; + } + + _maxDate = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + bool _enablePastDates; + + bool get enablePastDates => _enablePastDates; + + set enablePastDates(bool value) { + if (_enablePastDates == value) { + return; + } + + _enablePastDates = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + DateRangePickerSelectionShape _selectionShape; + + DateRangePickerSelectionShape get selectionShape => _selectionShape; + + set selectionShape(DateRangePickerSelectionShape value) { + if (_selectionShape == value) { + return; + } + + _selectionShape = value; + markNeedsPaint(); + } + + ValueNotifier _mouseHoverPosition; + + ValueNotifier get mouseHoverPosition => _mouseHoverPosition; + + set mouseHoverPosition(ValueNotifier value) { + if (_mouseHoverPosition == value) { + return; + } + + _mouseHoverPosition?.removeListener(markNeedsPaint); + _mouseHoverPosition = value; + markNeedsPaint(); + } + + double _selectionRadius; + + double get selectionRadius => _selectionRadius; + + set selectionRadius(double value) { + if (_selectionRadius == value) { + return; + } + + _selectionRadius = value; + markNeedsPaint(); + } + + bool _enableMultiView; + + bool get enableMultiView => _enableMultiView; + + set enableMultiView(bool value) { + if (_enableMultiView == value) { + return; + } + + _enableMultiView = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Specifies the space between the multi month views. + double _multiViewSpacing; + + double get multiViewSpacing => _multiViewSpacing; + + set multiViewSpacing(double value) { + if (_multiViewSpacing == value) { + return; + } + + _multiViewSpacing = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + double _textScaleFactor; + + double get textScaleFactor => _textScaleFactor; + + set textScaleFactor(double value) { + if (_textScaleFactor == value) { + return; + } + + _textScaleFactor = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + double _height; + + double get height => _height; + + set height(double value) { + if (_height == value) { + return; + } + + _height = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + double _width; + + double get width => _width; + + set width(double value) { + if (_width == value) { + return; + } + + _width = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + String _monthFormat; + + String get monthFormat => _monthFormat; + + set monthFormat(String value) { + if (_monthFormat == value) { + return; + } + + _monthFormat = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + Locale _locale; + + Locale get locale => _locale; + + set locale(Locale value) { + if (_locale == value) { + return; + } + + _locale = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + + DateRangePickerView _view; + + DateRangePickerView get view => _view; + + set view(DateRangePickerView value) { + if (_view == value) { + return; + } + + _view = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Defines the pickerType for [SfDateRangePicker]. + bool _isHijri; + + bool get isHijri => _isHijri; + + set isHijri(bool value) { + if (_isHijri == value) { + return; + } + + _isHijri = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + /// Specifies the localizations. + SfLocalizations localizations; + + /// Used to draw year cell text in month view. + TextPainter _textPainter; + + /// Used to paint the selection of year cell and today highlight on all + /// the selection mode. + Paint _todayHighlightPaint; + + /// attach will called when the render object rendered in view. + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _mouseHoverPosition?.addListener(markNeedsPaint); + } + + /// detach will called when the render object removed from view. + @override + void detach() { + _mouseHoverPosition?.removeListener(markNeedsPaint); + super.detach(); + } + + @override + void setupParentData(covariant RenderObject child) { + if (child.parentData is! _DatePickerParentData) { + child.parentData = _DatePickerParentData(); + } + } + + @override + void performLayout() { + final Size widgetSize = constraints.biggest; + size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height); + RenderBox child = firstChild; + if (child == null) { + return; + } + + double currentWidth = size.width; + double currentHeight = size.height; + if (_enableMultiView) { + if (_navigationDirection == + DateRangePickerNavigationDirection.horizontal) { + currentWidth = (currentWidth - multiViewSpacing) / 2; + } else { + currentHeight = (currentHeight - multiViewSpacing) / 2; + } + } + + final double cellWidth = currentWidth / YearView.maxColumnCount; + final double cellHeight = currentHeight / YearView.maxRowCount; + while (child != null) { + child.layout(constraints.copyWith( + minHeight: cellHeight, + maxHeight: cellHeight, + minWidth: cellWidth, + maxWidth: cellWidth)); + child = childAfter(child); + } + } + + @override + void paint(PaintingContext context, Offset offset); + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config.isSemanticBoundary = true; + } + + @override + void assembleSemanticsNode( + SemanticsNode node, + SemanticsConfiguration config, + Iterable children, + ) { + final List semantics = _getSemanticsBuilder(size); + final List semanticsNodes = []; + for (int i = 0; i < semantics.length; i++) { + final CustomPainterSemantics currentSemantics = semantics[i]; + final SemanticsNode newChild = SemanticsNode( + key: currentSemantics.key, + ); + + final SemanticsProperties properties = currentSemantics.properties; + final SemanticsConfiguration config = SemanticsConfiguration(); + if (properties.label != null) { + config.label = properties.label; + } + if (properties.textDirection != null) { + config.textDirection = properties.textDirection; + } + + newChild.updateWith( + config: config, + // As of now CustomPainter does not support multiple tree levels. + childrenInInversePaintOrder: const [], + ); + + newChild + ..rect = currentSemantics.rect + ..transform = currentSemantics.transform + ..tags = currentSemantics.tags; + + semanticsNodes.add(newChild); + } + + final List finalChildren = []; + finalChildren.addAll(semanticsNodes); + finalChildren.addAll(children); + + super.assembleSemanticsNode(node, config, finalChildren); + } + + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + return; + } + + List getSelectedIndex(int viewStartIndex, int viewEndIndex); + + void drawSelection( + Canvas canvas, + double cellWidth, + int currentIndex, + double highlightPadding, + double selectionPadding, + double textHalfHeight, + centerYPosition, + double xPosition, + double yPosition, + TextSpan yearText); + + /// draw selection when the cell have custom widget. + void drawCustomCellSelection(Canvas canvas, Rect rect, int index); + + List _getSemanticsBuilder(Size size) { + final List semanticsBuilder = + []; + double left, top; + Map leftAndTopValue; + int count = 1; + double width = size.width; + double height = size.height; + double webUIPadding = 0; + final bool isHorizontalMultiView = _enableMultiView && + _navigationDirection == DateRangePickerNavigationDirection.horizontal; + final bool isVerticalMultiView = _enableMultiView && + _navigationDirection == DateRangePickerNavigationDirection.vertical; + if (isHorizontalMultiView) { + webUIPadding = _multiViewSpacing; + count = 2; + width = (width - webUIPadding) / count; + } else if (isVerticalMultiView) { + webUIPadding = _multiViewSpacing; + count = 2; + height = (height - webUIPadding) / count; + } + + final double cellWidth = width / 3; + final double cellHeight = height / 4; + final int datesCount = visibleDates.length ~/ count; + for (int j = 0; j < count; j++) { + final int currentViewIndex = + 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 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)) { + leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( + isRtl, left, top, cellWidth, cellHeight, width); + left = leftAndTopValue['left']; + top = leftAndTopValue['top']; + continue; + } + + if (!DateRangePickerHelper.isBetweenMinMaxDateCell( + date, _minDate, _maxDate, _enablePastDates, _view, _isHijri)) { + 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); + 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, + ), + )); + leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( + isRtl, left, top, cellWidth, cellHeight, width); + left = leftAndTopValue['left']; + top = leftAndTopValue['top']; + } + } + + return semanticsBuilder; + } + + /// 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) { + int startIndex = -1; + int endIndex = -1; + final List selectedIndex = []; + + /// Check the start date as before of end date, if not then swap + /// the start and end date values. + if (startDate != null && startDate.isAfter(endDate)) { + final dynamic temp = startDate; + startDate = endDate; + endDate = temp; + } + + final dynamic viewStartDate = visibleDates[viewStartIndex]; + final dynamic viewEndDate = DateRangePickerHelper.getLastDate( + visibleDates[viewEndIndex], pickerView, _isHijri); + if (startDate != null) { + /// Assign start index as -1 when the start date before view start date. + if (viewStartDate.isAfter(startDate) && viewStartDate.isBefore(endDate)) { + startIndex = -1; + } else { + startIndex = DateRangePickerHelper.getDateCellIndex( + visibleDates, startDate, pickerView, + viewStartIndex: viewStartIndex, viewEndIndex: viewEndIndex); + } + } + + if (endDate != null) { + /// Assign end index as visible dates length when the + /// end date after of view end date. + if (viewEndDate.isAfter(startDate) && viewEndDate.isBefore(endDate)) { + endIndex = viewEndIndex + 1; + } else { + endIndex = DateRangePickerHelper.getDateCellIndex( + visibleDates, endDate, _view, + viewStartIndex: viewStartIndex, viewEndIndex: viewEndIndex); + } + } + + //// If some range end date as null then it end index is start index. + if (startIndex != -1 && endIndex == -1) { + endIndex = startIndex; + } + + /// Check the start index as before of end index, if not then swap + /// the start and end index values. + if (startIndex > endIndex) { + final int temp = startIndex; + startIndex = endIndex; + endIndex = temp; + } + + /// Add the index values in between start and end index values. + for (int i = startIndex; i <= endIndex; i++) { + selectedIndex.add(i); + } + + return selectedIndex; + } + + String _getCellText(dynamic date) { + if (_view == DateRangePickerView.year) { + final String format = + monthFormat == null || monthFormat.isEmpty ? 'MMM' : monthFormat; + if (isHijri) { + return DateRangePickerHelper.getHijriMonthText( + date, localizations, format); + } else { + return DateFormat(format, locale.toString()).format(date).toString(); + } + } else if (_view == DateRangePickerView.decade) { + return date.year.toString(); + } else if (_view == DateRangePickerView.century) { + return date.year.toString() + ' - ' + (date.year + 9).toString(); + } + + return ''; + } + + String getCellSemanticsText(dynamic date) { + if (_view == DateRangePickerView.year) { + if (isHijri) { + return DateRangePickerHelper.getHijriMonthText( + date, localizations, 'MMMM') + + date.year.toString(); + } else { + return DateFormat('MMMM yyyy').format(date).toString(); + } + } else if (_view == DateRangePickerView.decade) { + return date.year.toString(); + } else if (_view == DateRangePickerView.century) { + return date.year.toString() + ' to ' + (date.year + 9).toString(); + } + + return ''; + } + + 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) { + if (xPosition <= _mouseHoverPosition.value.dx && + xPosition + cellWidth >= _mouseHoverPosition.value.dx && + yPosition <= _mouseHoverPosition.value.dy && + yPosition + cellHeight >= _mouseHoverPosition.value.dy) { + _todayHighlightPaint = _todayHighlightPaint ?? Paint(); + _todayHighlightPaint.style = PaintingStyle.fill; + _todayHighlightPaint.strokeWidth = 2; + _todayHighlightPaint.color = selectionColor != null + ? selectionColor.withOpacity(0.4) + : datePickerTheme.selectionColor.withOpacity(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); + double cornerRadius = rect.height / 2; + switch (selectionShape) { + case DateRangePickerSelectionShape.rectangle: + { + cornerRadius = 3; + } + break; + case DateRangePickerSelectionShape.circle: + break; + } + + canvas.drawRRect( + RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), + _todayHighlightPaint); + } + } + + void _drawTodayHighlight( + Canvas canvas, + double cellWidth, + double cellHeight, + double centerYPosition, + double highlightPadding, + double selectionPadding, + double textHalfHeight, + double xPosition, + double yPosition) { + _todayHighlightPaint ??= Paint(); + _todayHighlightPaint.color = + todayHighlightColor ?? datePickerTheme.todayHighlightColor; + _todayHighlightPaint.isAntiAlias = true; + _todayHighlightPaint.strokeWidth = 1.0; + _todayHighlightPaint.style = PaintingStyle.stroke; + final double maximumHighlight = + centerYPosition - textHalfHeight - selectionPadding; + if (maximumHighlight < highlightPadding) { + highlightPadding = maximumHighlight; + } + + final Rect rect = Rect.fromLTRB( + xPosition + selectionPadding, + yPosition + centerYPosition - highlightPadding - textHalfHeight, + xPosition + cellWidth - selectionPadding, + yPosition + centerYPosition + highlightPadding + textHalfHeight); + double cornerRadius = rect.height / 2; + switch (selectionShape) { + case DateRangePickerSelectionShape.rectangle: + { + cornerRadius = 3; + } + break; + case DateRangePickerSelectionShape.circle: + break; + } + + canvas.drawRRect( + 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); + boxPainter.paint( + 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) { + if (!isEnableDate) { + return cellStyle.disabledDatesTextStyle ?? + datePickerTheme.disabledCellTextStyle; + } + + if (isSelected) { + return selectionTextStyle ?? datePickerTheme.selectionTextStyle; + } + + if (isCurrentDate) { + return cellStyle.todayTextStyle ?? datePickerTheme.todayCellTextStyle; + } + + if (!isActiveDate && !_isHijri) { + return cellStyle.leadingDatesTextStyle ?? + datePickerTheme.leadingCellTextStyle; + } + + return cellStyle.textStyle ?? datePickerTheme.cellTextStyle; + } + + Decoration _updateCellDecoration( + int j, bool isCurrentDate, bool isEnableDate, bool isActiveDate) { + if (!isEnableDate) { + return cellStyle.disabledDatesDecoration; + } + + if (isCurrentDate) { + return cellStyle.todayCellDecoration ?? cellStyle.cellDecoration; + } + + if (!isActiveDate && !_isHijri) { + return cellStyle.leadingDatesDecoration; + } + + return cellStyle.cellDecoration; + } +} + +class _SingleSelectionRenderObject extends _IYearViewRenderObject { + _SingleSelectionRenderObject( + List visibleDates, + 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) + : 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); + + dynamic _selectedDate; + + dynamic get selectedDate => _selectedDate; + + set selectedDate(dynamic value) { + if (isSameDate(_selectedDate, value)) { + return; + } + + _selectedDate = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + _drawYearCells(context, size, this); + } + + @override + void drawSelection( + Canvas canvas, + double cellWidth, + int currentIndex, + double highlightPadding, + double selectionPadding, + double textHalfHeight, + centerYPosition, + double xPosition, + double yPosition, + TextSpan yearText) { + _todayHighlightPaint ??= Paint(); + _todayHighlightPaint.isAntiAlias = true; + _todayHighlightPaint.style = PaintingStyle.fill; + final double maximumHighlight = + centerYPosition - textHalfHeight - selectionPadding; + if (maximumHighlight < highlightPadding) { + highlightPadding = maximumHighlight; + } + + final Rect rect = Rect.fromLTRB( + xPosition + selectionPadding, + yPosition + centerYPosition - highlightPadding - textHalfHeight, + xPosition + cellWidth - selectionPadding, + yPosition + centerYPosition + highlightPadding + textHalfHeight); + final double cornerRadius = + selectionShape == DateRangePickerSelectionShape.circle + ? rect.height / 2 + : 3; + _todayHighlightPaint.color = + selectionColor ?? datePickerTheme.selectionColor; + + canvas.drawRRect( + RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), + _todayHighlightPaint); + } + + @override + void drawCustomCellSelection(Canvas canvas, Rect rect, int index) { + _todayHighlightPaint ??= Paint(); + _todayHighlightPaint.isAntiAlias = true; + _todayHighlightPaint.style = PaintingStyle.fill; + _todayHighlightPaint.color = + selectionColor ?? datePickerTheme.selectionColor; + canvas.drawRect(rect, _todayHighlightPaint); + } + + @override + List getSelectedIndex(int viewStartIndex, int viewEndIndex) { + final List selectedIndex = []; + if (_selectedDate == null) { + return selectedIndex; + } + + final int index = DateRangePickerHelper.getDateCellIndex( + visibleDates, _selectedDate, _view, + viewStartIndex: viewStartIndex, viewEndIndex: viewEndIndex); + if (index != -1) { + selectedIndex.add(index); + } + + return selectedIndex; + } +} + +class _MultipleSelectionRenderObject extends _IYearViewRenderObject { + _MultipleSelectionRenderObject( + List visibleDates, + 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) + : 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); + + List _selectedDates; + + List get selectedDates => _selectedDates; + + set selectedDates(List value) { + if (DateRangePickerHelper.isDateCollectionEquals(_selectedDates, value)) { + return; + } + + _selectedDates = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + _drawYearCells(context, size, this); + } + + @override + void drawSelection( + Canvas canvas, + double cellWidth, + int currentIndex, + double highlightPadding, + double selectionPadding, + double textHalfHeight, + centerYPosition, + double xPosition, + double yPosition, + TextSpan yearText) { + _todayHighlightPaint ??= Paint(); + _todayHighlightPaint.isAntiAlias = true; + _todayHighlightPaint.style = PaintingStyle.fill; + final double maximumHighlight = + centerYPosition - textHalfHeight - selectionPadding; + if (maximumHighlight < highlightPadding) { + highlightPadding = maximumHighlight; + } + + final Rect rect = Rect.fromLTRB( + xPosition + selectionPadding, + yPosition + centerYPosition - highlightPadding - textHalfHeight, + xPosition + cellWidth - selectionPadding, + yPosition + centerYPosition + highlightPadding + textHalfHeight); + final double cornerRadius = + selectionShape == DateRangePickerSelectionShape.circle + ? rect.height / 2 + : 3; + _todayHighlightPaint.color = + selectionColor ?? datePickerTheme.selectionColor; + + canvas.drawRRect( + RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), + _todayHighlightPaint); + } + + @override + void drawCustomCellSelection(Canvas canvas, Rect rect, int index) { + _todayHighlightPaint ??= Paint(); + _todayHighlightPaint.isAntiAlias = true; + _todayHighlightPaint.style = PaintingStyle.fill; + _todayHighlightPaint.color = + selectionColor ?? datePickerTheme.selectionColor; + canvas.drawRect(rect, _todayHighlightPaint); + } + + @override + List getSelectedIndex(int viewStartIndex, int viewEndIndex) { + final List selectedIndex = []; + if (_selectedDates == null) { + return selectedIndex; + } + for (int i = 0; i < _selectedDates.length; i++) { + final int index = DateRangePickerHelper.getDateCellIndex( + visibleDates, _selectedDates[i], _view, + viewStartIndex: viewStartIndex, viewEndIndex: viewEndIndex); + if (index != -1) { + selectedIndex.add(index); + } + } + + return selectedIndex; + } +} + +class _RangeSelectionRenderObject extends _IYearViewRenderObject { + _RangeSelectionRenderObject( + List visibleDates, + 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) + : 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); + + dynamic _selectedRange; + + dynamic get selectedRange => _selectedRange; + + set selectedRange(dynamic value) { + if (DateRangePickerHelper.isRangeEquals(_selectedRange, value)) { + return; + } + + _selectedRange = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + List _selectedIndex; + + @override + void paint(PaintingContext context, Offset offset) { + _selectedIndex = []; + _drawYearCells(context, size, this); + } + + @override + void drawSelection( + Canvas canvas, + double cellWidth, + int currentIndex, + double highlightPadding, + double selectionPadding, + double textHalfHeight, + centerYPosition, + double xPosition, + double yPosition, + TextSpan yearText) { + _todayHighlightPaint ??= Paint(); + _todayHighlightPaint.isAntiAlias = true; + _todayHighlightPaint.style = PaintingStyle.fill; + final double maximumHighlight = + centerYPosition - textHalfHeight - selectionPadding; + if (maximumHighlight < highlightPadding) { + highlightPadding = maximumHighlight; + } + + final List selectionDetails = _getSelectedRangePosition(currentIndex); + 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); + final double leftRadius = isStartRange || isSelectedDate ? cornerRadius : 0; + final double rightRadius = isEndRange || isSelectedDate ? cornerRadius : 0; + if (isSelectedDate) { + _todayHighlightPaint.color = + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + } else if (isStartRange) { + _todayHighlightPaint.color = + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + } else if (isBetweenRange) { + yearText = TextSpan( + text: yearText.text, + style: rangeTextStyle ?? datePickerTheme.rangeSelectionTextStyle, + ); + + _todayHighlightPaint.color = + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + _textPainter.text = yearText; + _textPainter.layout(minWidth: cellWidth, 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); + } + + @override + void drawCustomCellSelection(Canvas canvas, Rect rect, int index) { + _todayHighlightPaint ??= Paint(); + _todayHighlightPaint.isAntiAlias = true; + _todayHighlightPaint.style = PaintingStyle.fill; + 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]; + if (isSelectedDate) { + _todayHighlightPaint.color = + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + } else if (isStartRange) { + _todayHighlightPaint.color = + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + } else if (isBetweenRange) { + _todayHighlightPaint.color = + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + } else if (isEndRange) { + _todayHighlightPaint.color = + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor; + } + canvas.drawRect(rect, _todayHighlightPaint); + } + + List _getSelectedRangePosition(int index) { + /// isSelectedDate value used to notify the year cell as selected and + /// the range hold only start date value on range and multi range selection. + bool isSelectedDate = false; + + /// isStartRange value used to notify the year cell as selected and + /// the year cell as start date cell of the picker date range. + /// its selection mode as range or multi range. + bool isStartRange = false; + + /// isEndRange value used to notify the year cell as selected and + /// the year cell as end date cell of the picker date range. + /// its selection mode as range or multi range. + bool isEndRange = false; + + /// isBetweenRange value used to notify the year cell as selected and + /// the year cell as in between the start and end date cell of the + /// picker date range. its selection mode as range or multi range. + bool isBetweenRange = false; + if (_selectedIndex.length == 1) { + isSelectedDate = true; + } else if (_selectedIndex[0] == index) { + if (isRtl) { + isEndRange = true; + } else { + isStartRange = true; + } + } else if (_selectedIndex[_selectedIndex.length - 1] == index) { + if (isRtl) { + isStartRange = true; + } else { + isEndRange = true; + } + } else { + isBetweenRange = true; + } + + return [isSelectedDate, isStartRange, isEndRange, isBetweenRange]; + } + + @override + List getSelectedIndex(int viewStartIndex, int viewEndIndex) { + _selectedIndex = []; + if (_selectedRange == null) { + return _selectedIndex; + } + + final dynamic startDate = _selectedRange.startDate; + final dynamic endDate = _selectedRange.endDate ?? _selectedRange.startDate; + _selectedIndex.addAll( + _getRangeIndex(startDate, endDate, view, viewStartIndex, viewEndIndex)); + + return _selectedIndex; + } +} + +class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { + _MultiRangeSelectionRenderObject( + List visibleDates, + 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) + : 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); + + List _selectedRanges; + + List get selectedRanges => _selectedRanges; + + set selectedRanges(List value) { + if (DateRangePickerHelper.isDateRangesEquals(_selectedRanges, value)) { + return; + } + + _selectedRanges = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + List> _rangesIndex; + + @override + void paint(PaintingContext context, Offset offset) { + _rangesIndex = >[]; + _drawYearCells(context, size, this); + } + + @override + void drawSelection( + Canvas canvas, + double cellWidth, + int currentIndex, + double highlightPadding, + double selectionPadding, + double textHalfHeight, + centerYPosition, + double xPosition, + double yPosition, + TextSpan yearText) { + _todayHighlightPaint ??= Paint(); + _todayHighlightPaint.isAntiAlias = true; + _todayHighlightPaint.style = PaintingStyle.fill; + final double maximumHighlight = + centerYPosition - textHalfHeight - selectionPadding; + if (maximumHighlight < highlightPadding) { + highlightPadding = maximumHighlight; + } + + final List selectionDetails = _getSelectedRangePosition(currentIndex); + 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); + final double leftRadius = isStartRange || isSelectedDate ? cornerRadius : 0; + final double rightRadius = isEndRange || isSelectedDate ? cornerRadius : 0; + if (isSelectedDate) { + _todayHighlightPaint.color = + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + } else if (isStartRange) { + _todayHighlightPaint.color = + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + } else if (isBetweenRange) { + yearText = TextSpan( + text: yearText.text, + style: rangeTextStyle ?? datePickerTheme.rangeSelectionTextStyle, + ); + + _todayHighlightPaint.color = + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + _textPainter.text = yearText; + _textPainter.layout(minWidth: cellWidth, 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); + } + + @override + void drawCustomCellSelection(Canvas canvas, Rect rect, int index) { + _todayHighlightPaint ??= Paint(); + _todayHighlightPaint.isAntiAlias = true; + _todayHighlightPaint.style = PaintingStyle.fill; + 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]; + if (isSelectedDate) { + _todayHighlightPaint.color = + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + } else if (isStartRange) { + _todayHighlightPaint.color = + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + } else if (isBetweenRange) { + _todayHighlightPaint.color = + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + } else if (isEndRange) { + _todayHighlightPaint.color = + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor; + } + canvas.drawRect(rect, _todayHighlightPaint); + } + + List _getSelectedRangePosition(int index) { + /// isSelectedDate value used to notify the year cell as selected and + /// the range hold only start date value on range and multi range selection. + bool isSelectedDate = false; + + /// isStartRange value used to notify the year cell as selected and + /// the year cell as start date cell of the picker date range. + /// its selection mode as range or multi range. + bool isStartRange = false; + + /// isEndRange value used to notify the year cell as selected and + /// the year cell as end date cell of the picker date range. + /// its selection mode as range or multi range. + bool isEndRange = false; + + /// isBetweenRange value used to notify the year cell as selected and + /// the year cell as in between the start and end date cell of the + /// picker date range. its selection mode as range or multi range. + bool isBetweenRange = false; + for (int i = 0; i < _rangesIndex.length; i++) { + final List range = _rangesIndex[i]; + if (!range.contains(index)) { + continue; + } + + if (range.length == 1) { + isSelectedDate = true; + } else if (range[0] == index) { + if (isRtl) { + isEndRange = true; + } else { + isStartRange = true; + } + } else if (range[range.length - 1] == index) { + if (isRtl) { + isStartRange = true; + } else { + isEndRange = true; + } + } else { + isBetweenRange = true; + } + + break; + } + + return [isSelectedDate, isStartRange, isEndRange, isBetweenRange]; + } + + @override + List getSelectedIndex(int viewStartIndex, int viewEndIndex) { + final List selectedIndex = []; + if (_selectedRanges == null) { + return selectedIndex; + } + + for (int i = 0; i < _selectedRanges.length; i++) { + final dynamic range = _selectedRanges[i]; + final dynamic startDate = range.startDate; + final dynamic endDate = range.endDate ?? range.startDate; + final List index = _getRangeIndex( + startDate, endDate, view, viewStartIndex, viewEndIndex); + _rangesIndex.add(index); + selectedIndex.addAll(index); + } + + return selectedIndex; + } +} + +/// 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); + + if (pickerView == DateRangePickerView.year) { + return true; + } + + final int datesCount = + enableMultiView ? visibleDates.length ~/ 2 : visibleDates.length; + final int middleIndex = (index * datesCount) + (datesCount ~/ 2); + final int currentYear = visibleDates[middleIndex].year; + if (pickerView == DateRangePickerView.decade) { + return currentYear ~/ 10 == date.year ~/ 10; + } else if (pickerView == DateRangePickerView.century) { + return currentYear ~/ 100 == date.year ~/ 100; + } + + return false; +} + +/// Draws the year cell on canvas based on selection mode. +void _drawYearCells( + 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 && + yearView.navigationDirection == + DateRangePickerNavigationDirection.horizontal; + final bool isVerticalMultiView = yearView.enableMultiView && + yearView.navigationDirection == + DateRangePickerNavigationDirection.vertical; + if (isHorizontalMultiView) { + webUIPadding = yearView.multiViewSpacing; + count = 2; + width = (width - webUIPadding) / count; + } else if (isVerticalMultiView) { + webUIPadding = yearView.multiViewSpacing; + count = 2; + height = (height - webUIPadding) / count; + } + + final int visibleDatesCount = yearView.visibleDates.length ~/ count; + final double cellWidth = width / YearView.maxColumnCount; + final double cellHeight = height / YearView.maxRowCount; + + double xPosition = 0, yPosition; + final bool isNeedWidgetPaint = yearView.childCount != 0; + final DateRangePickerView view = + DateRangePickerHelper.getPickerView(yearView.view); + + if (isNeedWidgetPaint) { + RenderBox child = yearView.firstChild; + for (int j = 0; j < count; j++) { + final int currentViewIndex = + yearView.isRtl ? DateRangePickerHelper.getRtlIndex(count, j) : j; + + final int viewStartIndex = j * visibleDatesCount; + 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 double viewStartPosition = isVerticalMultiView + ? 0 + : (currentViewIndex * width) + (currentViewIndex * webUIPadding); + final double viewEndPosition = viewStartPosition + width; + xPosition = viewStartPosition; + 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) + + (rowIndex * YearView.maxColumnCount); + } + + currentIndex += viewStartIndex; + if (xPosition >= viewEndPosition) { + xPosition = viewStartPosition; + yPosition += cellHeight; + } + + if ((yearView.enableMultiView || yearView.isHijri) && + DateRangePickerHelper.isLeadingCellDate( + currentIndex, viewStartIndex, yearView.visibleDates, view)) { + xPosition += cellWidth; + continue; + } + + 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); + + if (isSelected && isEnableDate) { + yearView.drawCustomCellSelection( + canvas, + Rect.fromLTRB(xPosition, yPosition, xPosition + cellWidth, + yPosition + cellHeight), + currentIndex); + } + + child.paint(context, Offset(xPosition, yPosition)); + + if (!isSelected && + isEnableDate && + yearView.mouseHoverPosition != null && + yearView.mouseHoverPosition.value != null) { + if (xPosition <= yearView.mouseHoverPosition.value.dx && + xPosition + cellWidth >= yearView.mouseHoverPosition.value.dx && + yPosition <= yearView.mouseHoverPosition.value.dy && + yPosition + cellHeight >= yearView.mouseHoverPosition.value.dy) { + yearView._todayHighlightPaint = + yearView._todayHighlightPaint ?? Paint(); + yearView._todayHighlightPaint.style = PaintingStyle.fill; + yearView._todayHighlightPaint.strokeWidth = 2; + yearView._todayHighlightPaint.color = + yearView.selectionColor != null + ? yearView.selectionColor.withOpacity(0.4) + : yearView.datePickerTheme.selectionColor.withOpacity(0.4); + + final Rect rect = Rect.fromLTRB(xPosition, yPosition, + xPosition + cellWidth, yPosition + cellHeight); + canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(2)), + yearView._todayHighlightPaint); + } + } + + xPosition += cellWidth; + child = yearView.childAfter(child); + } + } + return; + } + + final dynamic today = DateRangePickerHelper.getToday(yearView.isHijri); + yearView._textPainter ??= TextPainter( + textAlign: TextAlign.start, + textDirection: TextDirection.ltr, + maxLines: 2, + textScaleFactor: yearView.textScaleFactor, + textWidthBasis: TextWidthBasis.longestLine); + + const double decorationPadding = 1; + const double selectionPadding = 3; + final double centerYPosition = cellHeight / 2; + + for (int j = 0; j < count; j++) { + final int currentViewIndex = + yearView.isRtl ? DateRangePickerHelper.getRtlIndex(count, j) : j; + + final int viewStartIndex = j * visibleDatesCount; + 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 double viewStartPosition = isVerticalMultiView + ? 0 + : (currentViewIndex * width) + (currentViewIndex * webUIPadding); + final double viewEndPosition = viewStartPosition + width; + xPosition = viewStartPosition; + 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) + + (rowIndex * YearView.maxColumnCount); + } + + currentIndex += viewStartIndex; + if (xPosition >= viewEndPosition) { + xPosition = viewStartPosition; + yPosition += cellHeight; + } + + if ((yearView.enableMultiView || yearView.isHijri) && + DateRangePickerHelper.isLeadingCellDate( + currentIndex, viewStartIndex, yearView.visibleDates, view)) { + xPosition += cellWidth; + continue; + } + + final dynamic date = yearView.visibleDates[currentIndex]; + 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); + final bool isActiveDate = _isCurrentViewDateCell( + date, j, yearView.visibleDates, yearView.enableMultiView, view); + final TextStyle style = yearView._updateCellTextStyle( + j, isCurrentDate, isSelected, isEnableDate, isActiveDate); + final Decoration yearDecoration = yearView._updateCellDecoration( + j, isCurrentDate, isEnableDate, isActiveDate); + + final TextSpan yearText = TextSpan( + text: yearView._getCellText(date), + style: style, + ); + + yearView._textPainter.text = yearText; + yearView._textPainter.layout(minWidth: cellWidth, maxWidth: cellWidth); + + final double highlightPadding = yearView.selectionRadius ?? 10; + final double textHalfHeight = yearView._textPainter.height / 2; + if (isSelected && isEnableDate) { + yearView.drawSelection( + canvas, + cellWidth, + currentIndex, + highlightPadding, + selectionPadding, + textHalfHeight, + centerYPosition, + xPosition, + yPosition, + yearText); + } else if (yearDecoration != null) { + yearView._drawYearDecoration(canvas, yearDecoration, xPosition, + yPosition, decorationPadding, cellWidth, cellHeight); + } else if (isCurrentDate) { + yearView._drawTodayHighlight( + canvas, + cellWidth, + cellHeight, + centerYPosition, + highlightPadding, + selectionPadding, + textHalfHeight, + xPosition, + yPosition); + } + + double xOffset = + xPosition + ((cellWidth - yearView._textPainter.width) / 2); + xOffset = xOffset < 0 ? 0 : xOffset; + double yOffset = + yPosition + ((cellHeight - yearView._textPainter.height) / 2); + yOffset = yOffset < 0 ? 0 : yOffset; + + if (!isSelected && + isEnableDate && + yearView.mouseHoverPosition != null && + yearView.mouseHoverPosition.value != null) { + yearView._addMouseHovering( + canvas, + cellWidth, + cellHeight, + centerYPosition, + currentViewIndex, + width, + highlightPadding, + date, + selectionPadding, + textHalfHeight, + webUIPadding, + xOffset, + xPosition, + yOffset, + yPosition); + } + + yearView._textPainter.paint(canvas, Offset(xOffset, yOffset)); + xPosition += cellWidth; + } + } +} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/century_view.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/century_view.dart deleted file mode 100644 index 482342805..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/century_view.dart +++ /dev/null @@ -1,194 +0,0 @@ -part of datepicker; - -class _PickerCenturyViewPainter extends _IPickerYearView { - _PickerCenturyViewPainter( - this.visibleDates, - this.cellStyle, - this.minDate, - this.maxDate, - this.enablePastDates, - this.todayHighlightColor, - this.selectionShape, - 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.selectionMode, - this.selectionRadius, - this.selectionNotifier, - this.textScaleFactor) - : super(selectionNotifier: selectionNotifier); - - @override - final DateRangePickerYearCellStyle cellStyle; - @override - final List visibleDates; - @override - final bool isRtl; - @override - final Color todayHighlightColor; - @override - final DateTime minDate; - @override - final DateTime maxDate; - @override - final bool enablePastDates; - @override - final DateRangePickerSelectionShape selectionShape; - @override - final Offset mouseHoverPosition; - @override - final DateRangePickerSelectionMode selectionMode; - @override - final double selectionRadius; - @override - final bool enableMultiView; - @override - final double multiViewSpacing; - @override - final ValueNotifier selectionNotifier; - @override - final TextStyle selectionTextStyle; - @override - final TextStyle rangeTextStyle; - @override - final Color selectionColor; - @override - final Color startRangeSelectionColor; - @override - final Color endRangeSelectionColor; - @override - final Color rangeSelectionColor; - @override - dynamic selectedDate; - @override - TextPainter textPainter; - final Locale locale; - @override - Paint todayHighlightPaint; - @override - final SfDateRangePickerThemeData datePickerTheme; - @override - final double textScaleFactor; - - @override - void paint(Canvas canvas, Size size) { - _drawYearCells(canvas, size, this, DateRangePickerView.century); - } - - @override - bool _isSameView(DateTime date, DateTime currentDate) { - if (date == null || currentDate == null) { - return false; - } - - if (date.year ~/ 10 == currentDate.year ~/ 10) { - return true; - } - - return false; - } - - @override - bool _isTrailingDate(int index, int viewStartIndex) { - final DateTime currentDate = visibleDates[index]; - final DateTime viewStartDate = visibleDates[viewStartIndex]; - return currentDate.year ~/ 100 != viewStartDate.year ~/ 100; - } - - @override - bool _isBetweenMinMaxMonth(DateTime date) { - if (date == null || minDate == null || maxDate == null) { - return true; - } - - final int today = DateTime.now().year ~/ 10; - final int currentYear = date.year ~/ 10; - if (currentYear >= (minDate.year ~/ 10) && - currentYear <= (maxDate.year ~/ 10) && - (enablePastDates || (!enablePastDates && currentYear >= today))) { - return true; - } - - return false; - } - - @override - bool _isSameOrBeforeView(DateTime currentMaxDate, DateTime currentDate) { - return (currentDate.year ~/ 10) <= (currentMaxDate.year ~/ 10); - } - - @override - bool _isSameOrAfterView(DateTime currentMinDate, DateTime currentDate) { - return (currentDate.year ~/ 10) >= (currentMinDate.year ~/ 10); - } - - @override - void _updateSelection(_PickerStateArgs details) { - _updateYearViewSelection(this, details); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _PickerCenturyViewPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.todayHighlightColor != todayHighlightColor || - oldWidget.enablePastDates != enablePastDates || - oldWidget.cellStyle != cellStyle || - oldWidget.minDate != minDate || - oldWidget.maxDate != maxDate || - oldWidget.selectionShape != selectionShape || - oldWidget.isRtl != isRtl || - oldWidget.datePickerTheme != datePickerTheme || - oldWidget.enableMultiView != enableMultiView || - oldWidget.multiViewSpacing != multiViewSpacing || - oldWidget.selectedDate != selectedDate || - oldWidget.selectionMode != selectionMode || - oldWidget.rangeSelectionColor != rangeSelectionColor || - oldWidget.endRangeSelectionColor != endRangeSelectionColor || - oldWidget.startRangeSelectionColor != startRangeSelectionColor || - oldWidget.selectionColor != selectionColor || - oldWidget.rangeTextStyle != rangeTextStyle || - oldWidget.selectionTextStyle != selectionTextStyle || - oldWidget.selectionRadius != selectionRadius || - oldWidget.locale != locale || - oldWidget.textScaleFactor != textScaleFactor || - (kIsWeb && oldWidget.mouseHoverPosition != mouseHoverPosition); - } - - @override - String _getCellText(DateTime date) { - return date.year.toString() + ' - ' + (date.year + 9).toString(); - } - - @override - bool _isCurrentView(DateTime date, int index) { - final int datesCount = - enableMultiView ? visibleDates.length ~/ 2 : visibleDates.length; - final int middleIndex = (index * datesCount) + (datesCount ~/ 2); - final int currentYear = visibleDates[middleIndex].year; - return currentYear ~/ 100 == date.year ~/ 100; - } - - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilderForYearView(size, this); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _PickerCenturyViewPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates; - } -} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/decade_view.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/decade_view.dart deleted file mode 100644 index 6ae5a851f..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/decade_view.dart +++ /dev/null @@ -1,193 +0,0 @@ -part of datepicker; - -class _PickerDecadeViewPainter extends _IPickerYearView { - _PickerDecadeViewPainter( - this.visibleDates, - this.cellStyle, - this.minDate, - this.maxDate, - this.enablePastDates, - this.todayHighlightColor, - this.selectionShape, - 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.selectionMode, - this.selectionRadius, - this.selectionNotifier, - this.textScaleFactor) - : super(selectionNotifier: selectionNotifier); - - @override - final DateRangePickerYearCellStyle cellStyle; - @override - final List visibleDates; - @override - final bool isRtl; - @override - final Color todayHighlightColor; - @override - final DateTime minDate; - @override - final DateTime maxDate; - @override - final bool enablePastDates; - @override - final DateRangePickerSelectionShape selectionShape; - @override - final Offset mouseHoverPosition; - @override - final DateRangePickerSelectionMode selectionMode; - @override - final double selectionRadius; - @override - final bool enableMultiView; - @override - final double multiViewSpacing; - @override - final ValueNotifier selectionNotifier; - @override - final TextStyle selectionTextStyle; - @override - final TextStyle rangeTextStyle; - @override - final Color selectionColor; - @override - final Color startRangeSelectionColor; - @override - final Color endRangeSelectionColor; - @override - final Color rangeSelectionColor; - @override - dynamic selectedDate; - final Locale locale; - @override - TextPainter textPainter; - @override - Paint todayHighlightPaint; - @override - final SfDateRangePickerThemeData datePickerTheme; - @override - final double textScaleFactor; - - @override - void paint(Canvas canvas, Size size) { - _drawYearCells(canvas, size, this, DateRangePickerView.decade); - } - - @override - bool _isSameView(DateTime date, DateTime currentDate) { - if (date == null || currentDate == null) { - return false; - } - - if (date.year == currentDate.year) { - return true; - } - - return false; - } - - @override - bool _isBetweenMinMaxMonth(DateTime date) { - if (date == null || minDate == null || maxDate == null) { - return true; - } - - final DateTime today = DateTime.now(); - if (date.year >= minDate.year && - date.year <= maxDate.year && - (enablePastDates || (!enablePastDates && date.year >= today.year))) { - return true; - } - - return false; - } - - @override - bool _isSameOrBeforeView(DateTime currentMaxDate, DateTime currentDate) { - return currentDate.year <= currentMaxDate.year; - } - - @override - bool _isSameOrAfterView(DateTime currentMinDate, DateTime currentDate) { - return currentDate.year >= currentMinDate.year; - } - - @override - void _updateSelection(_PickerStateArgs details) { - _updateYearViewSelection(this, details); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _PickerDecadeViewPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.todayHighlightColor != todayHighlightColor || - oldWidget.enablePastDates != enablePastDates || - oldWidget.cellStyle != cellStyle || - oldWidget.minDate != minDate || - oldWidget.maxDate != maxDate || - oldWidget.selectionShape != selectionShape || - oldWidget.enableMultiView != enableMultiView || - oldWidget.multiViewSpacing != multiViewSpacing || - oldWidget.isRtl != isRtl || - oldWidget.datePickerTheme != datePickerTheme || - oldWidget.selectedDate != selectedDate || - oldWidget.selectionMode != selectionMode || - oldWidget.rangeSelectionColor != rangeSelectionColor || - oldWidget.endRangeSelectionColor != endRangeSelectionColor || - oldWidget.startRangeSelectionColor != startRangeSelectionColor || - oldWidget.selectionColor != selectionColor || - oldWidget.rangeTextStyle != rangeTextStyle || - oldWidget.selectionTextStyle != selectionTextStyle || - oldWidget.selectionRadius != selectionRadius || - oldWidget.locale != locale || - oldWidget.textScaleFactor != textScaleFactor || - (kIsWeb && oldWidget.mouseHoverPosition != mouseHoverPosition); - } - - @override - String _getCellText(DateTime date) { - return date.year.toString(); - } - - @override - bool _isTrailingDate(int index, int viewStartIndex) { - final DateTime currentDate = visibleDates[index]; - final DateTime viewStartDate = visibleDates[viewStartIndex]; - return currentDate.year ~/ 10 != viewStartDate.year ~/ 10; - } - - @override - bool _isCurrentView(DateTime date, int index) { - final int datesCount = - enableMultiView ? visibleDates.length ~/ 2 : visibleDates.length; - final int middleIndex = (index * datesCount) + (datesCount ~/ 2); - final int currentYear = visibleDates[middleIndex].year; - return currentYear ~/ 10 == date.year ~/ 10; - } - - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilderForYearView(size, this); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _PickerDecadeViewPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates; - } -} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/helper.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/helper.dart deleted file mode 100644 index 4caaef79b..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/helper.dart +++ /dev/null @@ -1,812 +0,0 @@ -part of datepicker; - -/// Update the year view selected date value from details value. -/// Notify the value changes to year view painter for repaint the painter. -void _updateYearViewSelection(_IPickerYearView view, _PickerStateArgs details) { - view.selectedDate = _getSelectedValue(view.selectionMode, details); - view.selectionNotifier.value = !view.selectionNotifier.value; -} - -/// Return the selected value from state details based on picker selection mode. -dynamic _getSelectedValue( - DateRangePickerSelectionMode mode, _PickerStateArgs details) { - switch (mode) { - case DateRangePickerSelectionMode.single: - return details._selectedDate; - case DateRangePickerSelectionMode.multiple: - return _cloneList(details._selectedDates); - case DateRangePickerSelectionMode.range: - return details._selectedRange; - case DateRangePickerSelectionMode.multiRange: - return _cloneList(details._selectedRanges); - } -} - -/// Check whether the date is in between the start and end date value. -bool _isDateWithInYearRange(DateTime startDate, DateTime endDate, DateTime date, - _IPickerYearView view) { - if (startDate == null || endDate == null || date == null) { - return false; - } - - /// Check the start date as before of end date, if not then swap - /// the start and end date values. - if (startDate.isAfter(endDate)) { - final DateTime temp = startDate; - startDate = endDate; - endDate = temp; - } - - /// Check the date is equal or after of the start date and - /// the date is equal or before of the end date. - if ((view._isSameView(endDate, date) || endDate.isAfter(date)) && - (view._isSameView(startDate, date) || startDate.isBefore(date))) { - return true; - } - - return false; -} - -/// Return index of the date value in dates collection. -/// Return -1 when the date does not exist in dates collection. -int _getYearCellIndex( - List dates, DateTime date, _IPickerYearView view, - {int viewStartIndex = -1, int viewEndIndex = -1}) { - if (date == null) { - return -1; - } - - viewStartIndex = viewStartIndex == -1 ? 0 : viewStartIndex; - viewEndIndex = viewEndIndex == -1 ? dates.length - 1 : viewEndIndex; - for (int i = viewStartIndex; i <= viewEndIndex; i++) { - final DateTime currentDate = dates[i]; - if (view._isSameView(date, currentDate)) { - return i; - } - } - - return -1; -} - -/// Return the previous view date of the date based on picker view. -/// Eg., if picker view as year view and view date as May, 2020 then -/// it return date value as Apr, 2020. -DateTime _getPreviousDate(DateRangePickerView view, DateTime viewDate) { - DateTime date; - switch (view) { - case DateRangePickerView.month: - break; - case DateRangePickerView.year: - { - date = DateTime(viewDate.year, viewDate.month, 1); - date = subtractDuration(date, const Duration(days: 1)); - return DateTime(date.year, date.month, 1); - } - case DateRangePickerView.decade: - { - date = DateTime(viewDate.year, 1, 1); - date = subtractDuration(date, const Duration(days: 1)); - return DateTime(date.year, 1, 1); - } - case DateRangePickerView.century: - { - date = DateTime((viewDate.year ~/ 10) * 10, 1, 1); - date = subtractDuration(date, const Duration(days: 1)); - return DateTime((date.year ~/ 10) * 10, 1, 1); - } - } - - return viewDate; -} - -/// Return the next view date of the date based on picker view. -/// Eg., if picker view as year view and view date as May, 2020 then -/// it return date value as Jun, 2020. -DateTime _getNextDate(DateRangePickerView view, DateTime viewEndDate) { - final DateTime date = _getLastDate(view, viewEndDate); - return addDuration(date, const Duration(days: 1)); -} - -/// Return the last date of the date based on picker view. -/// Eg., if picker view as year view and view date as May 20, 2020 then -/// it return date value as May 31, 2020. -DateTime _getLastDate(DateRangePickerView view, DateTime viewEndDate) { - switch (view) { - case DateRangePickerView.month: - break; - case DateRangePickerView.year: - { - final DateTime date = - DateTime(viewEndDate.year, viewEndDate.month + 1, 1); - return subtractDuration(date, const Duration(days: 1)); - } - case DateRangePickerView.decade: - { - final DateTime date = DateTime(viewEndDate.year + 1, 1, 1); - return subtractDuration(date, const Duration(days: 1)); - } - case DateRangePickerView.century: - { - final DateTime date = - DateTime(((viewEndDate.year ~/ 10) * 10) + 10, 1, 1); - return subtractDuration(date, const Duration(days: 1)); - } - } - - return viewEndDate; -} - -/// Return the first date of the date based on picker view. -/// Eg., if picker view as year view and view date as May 20, 2020 then -/// it return date value as May 1, 2020. -DateTime _getFirstDate(DateRangePickerView view, DateTime viewDate) { - switch (view) { - case DateRangePickerView.month: - break; - case DateRangePickerView.year: - { - return DateTime(viewDate.year, viewDate.month, 1); - } - case DateRangePickerView.decade: - { - return DateTime(viewDate.year, 1, 1); - } - case DateRangePickerView.century: - { - return DateTime((viewDate.year ~/ 10) * 10, 1, 1); - } - } - - return viewDate; -} - -/// Return list of int value in between start and end date index value. -List _getRangeIndex( - DateTime startDate, - DateTime endDate, - _IPickerYearView view, - DateRangePickerView pickerView, - int viewStartIndex, - int viewEndIndex) { - int startIndex = -1; - int endIndex = -1; - final List selectedIndex = []; - - /// Check the start date as before of end date, if not then swap - /// the start and end date values. - if (startDate != null && startDate.isAfter(endDate)) { - final DateTime temp = startDate; - startDate = endDate; - endDate = temp; - } - - final DateTime viewStartDate = view.visibleDates[viewStartIndex]; - final DateTime viewEndDate = - _getLastDate(pickerView, view.visibleDates[viewEndIndex]); - if (startDate != null) { - /// Assign start index as -1 when the start date before of view start date. - if (viewStartDate.isAfter(startDate) && viewStartDate.isBefore(endDate)) { - startIndex = -1; - } else { - startIndex = _getYearCellIndex(view.visibleDates, startDate, view, - viewStartIndex: viewStartIndex, viewEndIndex: viewEndIndex); - } - } - - if (endDate != null) { - /// Assign end index as visible dates length when the - /// end date after of view end date. - if (viewEndDate.isAfter(startDate) && viewEndDate.isBefore(endDate)) { - endIndex = viewEndIndex + 1; - } else { - endIndex = _getYearCellIndex(view.visibleDates, endDate, view, - viewStartIndex: viewStartIndex, viewEndIndex: viewEndIndex); - } - } - - //// If some range end date as null then it end index is start index. - if (startIndex != -1 && endIndex == -1) { - endIndex = startIndex; - } - - /// Check the start index as before of end index, if not then swap - /// the start and end index values. - if (startIndex > endIndex) { - final int temp = startIndex; - startIndex = endIndex; - endIndex = temp; - } - - /// Add the index values in between start and end index values. - for (int i = startIndex; i <= endIndex; i++) { - selectedIndex.add(i); - } - - return selectedIndex; -} - -void _drawYearSelection( - _IPickerYearView view, - int viewStartIndex, - int viewEndIndex, - List selectedIndex, - List> rangeIndex, - DateRangePickerView pickerView) { - switch (view.selectionMode) { - case DateRangePickerSelectionMode.single: - { - final int index = _getYearCellIndex( - view.visibleDates, view.selectedDate, view, - viewStartIndex: viewStartIndex, viewEndIndex: viewEndIndex); - if (index != -1) { - selectedIndex.add(index); - } - } - break; - case DateRangePickerSelectionMode.multiple: - { - for (int i = 0; i < view.selectedDate.length; i++) { - final int index = _getYearCellIndex( - view.visibleDates, view.selectedDate[i], view, - viewStartIndex: viewStartIndex, viewEndIndex: viewEndIndex); - if (index != -1) { - selectedIndex.add(index); - } - } - } - break; - case DateRangePickerSelectionMode.range: - { - final DateTime startDate = view.selectedDate.startDate; - final DateTime endDate = - view.selectedDate.endDate ?? view.selectedDate.startDate; - selectedIndex.clear(); - selectedIndex.addAll(_getRangeIndex(startDate, endDate, view, - pickerView, viewStartIndex, viewEndIndex)); - rangeIndex.add(selectedIndex); - } - break; - case DateRangePickerSelectionMode.multiRange: - { - for (int i = 0; i < view.selectedDate.length; i++) { - final PickerDateRange range = view.selectedDate[i]; - final DateTime startDate = range.startDate; - final DateTime endDate = range.endDate ?? range.startDate; - final List index = _getRangeIndex(startDate, endDate, view, - pickerView, viewStartIndex, viewEndIndex); - rangeIndex.add(index); - for (int j = 0; j < index.length; j++) { - selectedIndex.add(index[j]); - } - } - } - } -} - -/// Draws the year cell on canvas based on picker view and [_IPickerYearView] -/// value -void _drawYearCells(Canvas canvas, Size size, _IPickerYearView view, - DateRangePickerView pickerView) { - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - double width = size.width; - final double webUIPadding = view.multiViewSpacing; - if (view.enableMultiView) { - width = (width - webUIPadding) / 2; - } - - final double cellWidth = width / _IPickerYearView._maxColumnCount; - final double cellHeight = size.height / _IPickerYearView._maxRowCount; - - double xPosition = 0, yPosition; - final DateTime today = DateTime.now(); - view.textPainter ??= TextPainter( - textAlign: TextAlign.start, - textDirection: TextDirection.ltr, - maxLines: 2, - textScaleFactor: view.textScaleFactor, - textWidthBasis: TextWidthBasis.longestLine); - - const double decorationPadding = 1; - const double selectionPadding = 3; - final double centerYPosition = cellHeight / 2; - - final int visibleDatesCount = view.enableMultiView - ? view.visibleDates.length ~/ 2 - : view.visibleDates.length; - for (int j = 0; j < (view.enableMultiView ? 2 : 1); j++) { - final int currentViewIndex = - view.isRtl ? _getRtlIndex(view.enableMultiView ? 2 : 1, j) : j; - final List selectedIndex = []; - final List> rangeIndex = >[]; - - final int viewStartIndex = j * visibleDatesCount; - final int viewEndIndex = ((j + 1) * visibleDatesCount) - 1; - - /// Calculate the selected index values based on selected date property in - /// [_IPickerYearView] - if (view.selectedDate != null) { - _drawYearSelection(view, viewStartIndex, viewEndIndex, selectedIndex, - rangeIndex, pickerView); - } - - final double viewStartPosition = - (currentViewIndex * width) + (currentViewIndex * webUIPadding); - final double viewEndPosition = - ((currentViewIndex + 1) * width) + (currentViewIndex * webUIPadding); - xPosition = viewStartPosition; - yPosition = 0; - for (int i = 0; i < visibleDatesCount; i++) { - int currentIndex = i; - if (view.isRtl) { - final int rowIndex = i ~/ _IPickerYearView._maxColumnCount; - currentIndex = _getRtlIndex(_IPickerYearView._maxColumnCount, - i % _IPickerYearView._maxColumnCount) + - (rowIndex * _IPickerYearView._maxColumnCount); - } - - currentIndex += viewStartIndex; - if (xPosition >= viewEndPosition) { - xPosition = viewStartPosition; - yPosition += cellHeight; - } - - if (view.enableMultiView && - view._isTrailingDate(currentIndex, viewStartIndex)) { - xPosition += cellWidth; - continue; - } - - final DateTime date = view.visibleDates[currentIndex]; - final bool isCurrentDate = view._isSameView(date, today); - final bool isSelected = selectedIndex.contains(currentIndex); - final bool isEnableDate = view._isBetweenMinMaxMonth(date); - final bool isActiveDate = view._isCurrentView(date, j); - final TextStyle style = _updateCellTextStyle( - view, j, isCurrentDate, isSelected, isEnableDate, isActiveDate); - final Decoration yearDecoration = _updateCellDecoration( - view, j, isCurrentDate, isEnableDate, isActiveDate); - - final TextSpan yearText = TextSpan( - text: view._getCellText(date), - style: style, - ); - - view.textPainter.text = yearText; - view.textPainter.layout(minWidth: 0, maxWidth: cellWidth); - - final double highlightPadding = view.selectionRadius ?? 10; - final double textHalfHeight = view.textPainter.height / 2; - if (isSelected && isEnableDate) { - _drawYearCellsAndSelection( - canvas, - cellWidth, - currentIndex, - highlightPadding, - rangeIndex, - selectionPadding, - textHalfHeight, - centerYPosition, - view, - xPosition, - yPosition, - yearText); - } else if (yearDecoration != null) { - _drawYearDecoration(canvas, yearDecoration, xPosition, yPosition, - decorationPadding, cellWidth, cellHeight); - } else if (isCurrentDate) { - _drawTodayHighlight( - canvas, - view, - cellWidth, - cellHeight, - centerYPosition, - highlightPadding, - selectionPadding, - textHalfHeight, - xPosition, - yPosition); - } - - double xOffset = xPosition + ((cellWidth - view.textPainter.width) / 2); - xOffset = xOffset < 0 ? 0 : xOffset; - double yOffset = yPosition + ((cellHeight - view.textPainter.height) / 2); - yOffset = yOffset < 0 ? 0 : yOffset; - - if (view.mouseHoverPosition != null) { - if (!view._isBetweenMinMaxMonth(date)) { - view.textPainter.paint(canvas, Offset(xOffset, yOffset)); - xPosition += cellWidth; - continue; - } - _addMouseHovering( - canvas, - cellWidth, - cellHeight, - centerYPosition, - currentViewIndex, - width, - highlightPadding, - date, - selectionPadding, - textHalfHeight, - view, - webUIPadding, - xOffset, - xPosition, - yOffset, - yPosition); - } - - view.textPainter.paint(canvas, Offset(xOffset, yOffset)); - xPosition += cellWidth; - } - } -} - -void _addMouseHovering( - Canvas canvas, - double cellWidth, - double cellHeight, - double centerYPosition, - int currentViewIndex, - double width, - double highlightPadding, - DateTime date, - double selectionPadding, - double textHalfHeight, - _IPickerYearView view, - double webUIPadding, - double xOffset, - double xPosition, - double yOffset, - double yPosition) { - if (xPosition <= view.mouseHoverPosition.dx && - xPosition + cellWidth >= view.mouseHoverPosition.dx && - yPosition - 5 <= view.mouseHoverPosition.dy && - (yPosition + cellHeight) - 5 >= view.mouseHoverPosition.dy) { - view.todayHighlightPaint = view.todayHighlightPaint ?? Paint(); - view.todayHighlightPaint.style = PaintingStyle.fill; - view.todayHighlightPaint.strokeWidth = 2; - view.todayHighlightPaint.color = view.selectionColor != null - ? view.selectionColor.withOpacity(0.4) - : view.datePickerTheme.selectionColor.withOpacity(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); - double cornerRadius = rect.height / 2; - switch (view.selectionShape) { - case DateRangePickerSelectionShape.rectangle: - { - cornerRadius = 3; - } - break; - case DateRangePickerSelectionShape.circle: - break; - } - - canvas.drawRRect( - RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), - view.todayHighlightPaint); - } -} - -void _drawTodayHighlight( - Canvas canvas, - _IPickerYearView view, - double cellWidth, - double cellHeight, - double centerYPosition, - double highlightPadding, - double selectionPadding, - double textHalfHeight, - double xPosition, - double yPosition) { - view.todayHighlightPaint ??= Paint(); - view.todayHighlightPaint.color = - view.todayHighlightColor ?? view.datePickerTheme.todayHighlightColor; - view.todayHighlightPaint.isAntiAlias = true; - view.todayHighlightPaint.strokeWidth = 1.0; - view.todayHighlightPaint.style = PaintingStyle.stroke; - final double maximumHighlight = - centerYPosition - textHalfHeight - selectionPadding; - if (maximumHighlight < highlightPadding) { - highlightPadding = maximumHighlight; - } - - final Rect rect = Rect.fromLTRB( - xPosition + selectionPadding, - yPosition + centerYPosition - highlightPadding - textHalfHeight, - xPosition + cellWidth - selectionPadding, - yPosition + centerYPosition + highlightPadding + textHalfHeight); - double cornerRadius = rect.height / 2; - switch (view.selectionShape) { - case DateRangePickerSelectionShape.rectangle: - { - cornerRadius = 3; - } - break; - case DateRangePickerSelectionShape.circle: - break; - } - - canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), - view.todayHighlightPaint); -} - -void _drawYearDecoration( - Canvas canvas, - Decoration yearDecoration, - double xPosition, - double yPosition, - double decorationPadding, - double cellWidth, - double cellHeight) { - final BoxPainter boxPainter = yearDecoration.createBoxPainter(); - boxPainter.paint( - canvas, - Offset(xPosition + decorationPadding, yPosition + decorationPadding), - ImageConfiguration( - size: Size(cellWidth - (2 * decorationPadding), - cellHeight - (2 * decorationPadding)))); -} - -void _drawYearCellsAndSelection( - Canvas canvas, - double cellWidth, - int currentIndex, - double highlightPadding, - List> rangeIndex, - double selectionPadding, - double textHalfHeight, - centerYPosition, - _IPickerYearView view, - double xPosition, - double yPosition, - TextSpan yearText) { - view.todayHighlightPaint ??= Paint(); - view.todayHighlightPaint.isAntiAlias = true; - view.todayHighlightPaint.style = PaintingStyle.fill; - final double maximumHighlight = - centerYPosition - textHalfHeight - selectionPadding; - if (maximumHighlight < highlightPadding) { - highlightPadding = maximumHighlight; - } - - /// isSelectedDate value used to notify the year cell as selected and - /// its selection mode as single or multiple or the [PickerDateRange] - /// holds only start date value on range and multi range selection. - bool isSelectedDate = - view.selectionMode == DateRangePickerSelectionMode.single || - view.selectionMode == DateRangePickerSelectionMode.multiple - ? true - : false; - - /// isStartRange value used to notify the year cell as selected and - /// the year cell as start date cell of the [PickerDateRange]. - /// its selection mode as range or multi range. - bool isStartRange = false; - - /// isEndRange value used to notify the year cell as selected and - /// the year cell as end date cell of the [PickerDateRange]. - /// its selection mode as range or multi range. - bool isEndRange = false; - - /// isBetweenRange value used to notify the year cell as selected and - /// the year cell as in between the start and end date cell of the - /// [PickerDateRange]. its selection mode as range or multi range. - bool isBetweenRange = false; - switch (view.selectionMode) { - case DateRangePickerSelectionMode.single: - case DateRangePickerSelectionMode.multiple: - break; - case DateRangePickerSelectionMode.range: - case DateRangePickerSelectionMode.multiRange: - List range = []; - for (int k = 0; k < rangeIndex.length; k++) { - range = rangeIndex[k]; - if (!range.contains(currentIndex)) { - continue; - } - - if (range.length == 1) { - isSelectedDate = true; - } else if (range[0] == currentIndex) { - if (view.isRtl) { - isEndRange = true; - } else { - isStartRange = true; - } - } else if (range[range.length - 1] == currentIndex) { - if (view.isRtl) { - isStartRange = true; - } else { - isEndRange = true; - } - } else { - isBetweenRange = true; - } - } - } - - 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 - : (view.selectionShape == DateRangePickerSelectionShape.circle - ? rect.height / 2 - : 3); - final double leftRadius = isStartRange || isSelectedDate ? cornerRadius : 0; - final double rightRadius = isEndRange || isSelectedDate ? cornerRadius : 0; - if (isSelectedDate) { - switch (view.selectionMode) { - case DateRangePickerSelectionMode.single: - case DateRangePickerSelectionMode.multiple: - { - view.todayHighlightPaint.color = - view.selectionColor ?? view.datePickerTheme.selectionColor; - } - break; - case DateRangePickerSelectionMode.range: - case DateRangePickerSelectionMode.multiRange: - { - view.todayHighlightPaint.color = view.startRangeSelectionColor ?? - view.datePickerTheme.startRangeSelectionColor; - } - } - } else if (isStartRange) { - view.todayHighlightPaint.color = view.startRangeSelectionColor ?? - view.datePickerTheme.startRangeSelectionColor; - } else if (isBetweenRange) { - yearText = TextSpan( - text: yearText.text, - style: - view.rangeTextStyle ?? view.datePickerTheme.rangeSelectionTextStyle, - ); - - view.textPainter.text = yearText; - view.textPainter.layout(minWidth: 0, maxWidth: cellWidth); - - view.todayHighlightPaint.color = - view.rangeSelectionColor ?? view.datePickerTheme.rangeSelectionColor; - } else if (isEndRange) { - view.todayHighlightPaint.color = view.endRangeSelectionColor ?? - view.datePickerTheme.endRangeSelectionColor; - } - - canvas.drawRRect( - RRect.fromRectAndCorners(rect, - topLeft: Radius.circular(leftRadius), - bottomLeft: Radius.circular(leftRadius), - bottomRight: Radius.circular(rightRadius), - topRight: Radius.circular(rightRadius)), - view.todayHighlightPaint); -} - -TextStyle _updateCellTextStyle(_IPickerYearView view, int j, bool isCurrentDate, - bool isSelected, bool isEnableDate, bool isActiveDate) { - if (!isEnableDate) { - return view.cellStyle.disabledDatesTextStyle ?? - view.datePickerTheme.disabledCellTextStyle; - } - - if (isSelected) { - return view.selectionTextStyle ?? view.datePickerTheme.selectionTextStyle; - } - - if (isCurrentDate) { - return view.cellStyle.todayTextStyle ?? - view.datePickerTheme.todayCellTextStyle; - } - - if (!isActiveDate) { - return view.cellStyle.leadingDatesTextStyle ?? - view.datePickerTheme.leadingCellTextStyle; - } - - return view.cellStyle.textStyle ?? view.datePickerTheme.cellTextStyle; -} - -Decoration _updateCellDecoration(_IPickerYearView view, int j, - bool isCurrentDate, bool isEnableDate, bool isActiveDate) { - if (!isEnableDate) { - return view.cellStyle.disabledDatesDecoration; - } - - if (isCurrentDate) { - return view.cellStyle.todayCellDecoration; - } - - if (!isActiveDate) { - return view.cellStyle.leadingDatesDecoration; - } - - return view.cellStyle.cellDecoration; -} - -List _getSemanticsBuilderForYearView( - Size size, _IPickerYearView view) { - final List semanticsBuilder = - []; - double left, top; - Map leftAndTopValue; - int count = 1; - double width = size.width; - if (view.enableMultiView) { - count = 2; - width = (size.width - view.multiViewSpacing) / 2; - } - - final double cellWidth = width / 3; - final double cellHeight = size.height / 4; - final int datesCount = view.visibleDates.length ~/ count; - for (int j = 0; j < count; j++) { - final int currentViewIndex = view.isRtl ? _getRtlIndex(count, j) : j; - left = view.isRtl ? width - cellWidth : 0; - top = 0; - final double startXPosition = - (currentViewIndex * width) + (currentViewIndex * view.multiViewSpacing); - final int startIndex = j * datesCount; - for (int i = 0; i < datesCount; i++) { - final DateTime date = view.visibleDates[startIndex + i]; - if (view._isTrailingDate(startIndex + i, startIndex)) { - leftAndTopValue = _getTopAndLeftValues( - view.isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; - continue; - } - - if (!view._isBetweenMinMaxMonth(date)) { - semanticsBuilder.add(CustomPainterSemantics( - rect: - Rect.fromLTWH(startXPosition + left, top, cellWidth, cellHeight), - properties: SemanticsProperties( - label: _getCellText(date, view) + 'Disabled cell', - textDirection: TextDirection.ltr, - ), - )); - - leftAndTopValue = _getTopAndLeftValues( - view.isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; - continue; - } - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(startXPosition + left, top, cellWidth, cellHeight), - properties: SemanticsProperties( - label: _getCellText(date, view), - textDirection: TextDirection.ltr, - ), - )); - leftAndTopValue = _getTopAndLeftValues( - view.isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; - } - } - - return semanticsBuilder; -} - -String _getCellText(DateTime date, _IPickerYearView view) { - if (view.runtimeType.toString() == '_PickerYearViewPainter') { - return DateFormat('MMMM yyyy').format(date).toString(); - } else if (view.runtimeType.toString() == '_PickerDecadeViewPainter') { - return date.year.toString(); - } else { - return date.year.toString() + ' to ' + (date.year + 9).toString(); - } -} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/interface.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/interface.dart deleted file mode 100644 index 75e6b3b27..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/interface.dart +++ /dev/null @@ -1,83 +0,0 @@ -part of datepicker; - -abstract class _IPickerYearView extends CustomPainter { - _IPickerYearView({ValueNotifier selectionNotifier}) - : super(repaint: selectionNotifier); - DateRangePickerYearCellStyle cellStyle; - TextStyle selectionTextStyle; - TextStyle rangeTextStyle; - Color selectionColor; - Color startRangeSelectionColor; - Color endRangeSelectionColor; - Color rangeSelectionColor; - List visibleDates; - bool isRtl; - Color todayHighlightColor; - DateTime minDate; - DateTime maxDate; - bool enablePastDates; - DateRangePickerSelectionShape selectionShape; - SfDateRangePickerThemeData datePickerTheme; - TextPainter textPainter; - Paint todayHighlightPaint; - Offset mouseHoverPosition; - ValueNotifier selectionNotifier; - dynamic selectedDate; - double selectionRadius; - DateRangePickerSelectionMode selectionMode; - bool enableMultiView; - double multiViewSpacing; - double textScaleFactor; - static const int _maxColumnCount = 3; - static const int _maxRowCount = 4; - - @override - void paint(Canvas canvas, Size size); - - /// Update selection method used to update the selected date value and - /// call the draw method to update selection on year, decade and century views - void _updateSelection(_PickerStateArgs details); - - @override - bool shouldRepaint(CustomPainter oldDelegate); - - /// Check whether the dates are in same view or not. - /// eg., In year view 05-03-2020 and 18-03-2020 are different dates - /// but they are in same cell on year view. - bool _isSameView(DateTime date, DateTime currentDate); - - /// Check whether the dates are in same or before of current view or not. - /// eg., In year view 05-03-2020 and 18-03-2020 are different dates - /// but they are in same cell on year view. In year view 05-02-2020 is before - /// of 05-03-2020. - bool _isSameOrBeforeView(DateTime date, DateTime currentDate); - - /// Check whether the dates are in same or after of current view or not. - /// eg., In year view 05-03-2020 and 18-03-2020 are different dates - /// but they are in same cell on year view. In year view 05-03-2020 is after - /// of 05-02-2020. - bool _isSameOrAfterView(DateTime date, DateTime currentDate); - - /// Check whether the date is in between the minimum and maximum dates - bool _isBetweenMinMaxMonth(DateTime date); - - /// Return the string value used to rendering the text on view cell on year, - /// decade and century views - String _getCellText(DateTime date); - - /// Check the given index as trailing index or not. - bool _isTrailingDate(int index, int viewStartIndex); - - /// Check whether the date as current view date or other view date. - bool _isCurrentView(DateTime date, int index); - - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the list - /// of custom painter semantics which contains the rect area and the semantics - /// properties for accessibility - @override - SemanticsBuilderCallback get semanticsBuilder; - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate); -} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/year_view.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/year_view.dart deleted file mode 100644 index 1cc01e449..000000000 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view/year_view.dart +++ /dev/null @@ -1,203 +0,0 @@ -part of datepicker; - -class _PickerYearViewPainter extends _IPickerYearView { - _PickerYearViewPainter( - 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.selectionMode, - this.selectionRadius, - this.selectionNotifier, - this.textScaleFactor) - : super(selectionNotifier: selectionNotifier); - - @override - final DateRangePickerYearCellStyle cellStyle; - @override - final List visibleDates; - @override - final bool isRtl; - final String monthFormat; - @override - final Color todayHighlightColor; - @override - final DateTime minDate; - @override - final DateTime maxDate; - @override - final bool enablePastDates; - @override - final DateRangePickerSelectionShape selectionShape; - final Locale locale; - @override - final Offset mouseHoverPosition; - @override - final DateRangePickerSelectionMode selectionMode; - @override - final double selectionRadius; - @override - final bool enableMultiView; - @override - final double multiViewSpacing; - @override - final ValueNotifier selectionNotifier; - @override - final TextStyle selectionTextStyle; - @override - final TextStyle rangeTextStyle; - @override - final Color selectionColor; - @override - final Color startRangeSelectionColor; - @override - final Color endRangeSelectionColor; - @override - final Color rangeSelectionColor; - @override - dynamic selectedDate; - @override - TextPainter textPainter; - @override - Paint todayHighlightPaint; - @override - final SfDateRangePickerThemeData datePickerTheme; - @override - final double textScaleFactor; - - @override - void paint(Canvas canvas, Size size) { - _drawYearCells(canvas, size, this, DateRangePickerView.year); - } - - @override - bool _isSameView(DateTime date, DateTime currentDate) { - if (date == null || currentDate == null) { - return false; - } - - if (date.month == currentDate.month && date.year == currentDate.year) { - return true; - } - - return false; - } - - @override - bool _isTrailingDate(int index, int viewStartIndex) { - return false; - } - - @override - bool _isBetweenMinMaxMonth(DateTime date) { - if (date == null || minDate == null || maxDate == null) { - return true; - } - - final DateTime today = DateTime.now(); - if (((date.month >= minDate.month && date.year == minDate.year) || - date.year > minDate.year) && - ((date.month <= maxDate.month && date.year == maxDate.year) || - date.year < maxDate.year) && - (enablePastDates || - (!enablePastDates && - ((date.month >= today.month && date.year == today.year) || - date.year > today.year)))) { - return true; - } - - return false; - } - - @override - bool _isSameOrBeforeView(DateTime currentMaxDate, DateTime currentDate) { - return (currentDate.month <= currentMaxDate.month && - currentDate.year == currentMaxDate.year) || - currentDate.year < currentMaxDate.year; - } - - @override - bool _isSameOrAfterView(DateTime currentMinDate, DateTime currentDate) { - return (currentDate.month >= currentMinDate.month && - currentDate.year == currentMinDate.year) || - currentDate.year > currentMinDate.year; - } - - @override - void _updateSelection(_PickerStateArgs details) { - _updateYearViewSelection(this, details); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _PickerYearViewPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.todayHighlightColor != todayHighlightColor || - oldWidget.enablePastDates != enablePastDates || - oldWidget.cellStyle != cellStyle || - oldWidget.minDate != minDate || - oldWidget.maxDate != maxDate || - oldWidget.enableMultiView != enableMultiView || - oldWidget.multiViewSpacing != multiViewSpacing || - oldWidget.selectionShape != selectionShape || - oldWidget.monthFormat != monthFormat || - oldWidget.isRtl != isRtl || - oldWidget.datePickerTheme != datePickerTheme || - oldWidget.selectedDate != selectedDate || - oldWidget.selectionMode != selectionMode || - oldWidget.rangeSelectionColor != rangeSelectionColor || - oldWidget.endRangeSelectionColor != endRangeSelectionColor || - oldWidget.startRangeSelectionColor != startRangeSelectionColor || - oldWidget.selectionColor != selectionColor || - oldWidget.rangeTextStyle != rangeTextStyle || - oldWidget.selectionTextStyle != selectionTextStyle || - oldWidget.selectionRadius != selectionRadius || - oldWidget.locale != locale || - oldWidget.textScaleFactor != textScaleFactor || - (kIsWeb && oldWidget.mouseHoverPosition != mouseHoverPosition); - } - - @override - String _getCellText(DateTime date) { - return DateFormat( - monthFormat == null || monthFormat.isEmpty ? 'MMM' : monthFormat, - locale.toString()) - .format(date) - .toString(); - } - - @override - bool _isCurrentView(DateTime date, int index) { - return true; - } - - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilderForYearView(size, this); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _PickerYearViewPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates; - } -} 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 346b87ad4..eea3c9270 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 @@ -40,7 +40,7 @@ class GaugeAnnotation { /// /// Applied widget added over a gauge with respect to [angle] or [axisValue]. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart ///Widget build(BuildContext context) { @@ -62,7 +62,7 @@ class GaugeAnnotation { /// The annotation is positioned on the basis of the calculated /// direction and distance. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart ///Widget build(BuildContext context) { @@ -83,7 +83,7 @@ class GaugeAnnotation { /// * [GaugeAlignment.near] aligns the annotation widget to near. /// * [GaugeAlignment.far] aligns the annotation widget to far. /// - /// Defaults to [GaugeAlignment.center] + /// Defaults to `GaugeAlignment.center`. /// /// Also refer [GaugeAlignment] /// @@ -107,7 +107,7 @@ class GaugeAnnotation { /// * [GaugeAlignment.near] aligns the annotation widget to near. /// * [GaugeAlignment.far] aligns the annotation widget to far. /// - /// Defaults to [GaugeAlignment.center] + /// Defaults to `GaugeAlignment.center`. /// /// Also refer [GaugeAlignment] /// @@ -131,7 +131,7 @@ class GaugeAnnotation { /// /// [positionFactor] must be between 0 to 1. /// - /// Defaults to 0 + /// Defaults to `0`. /// ///```dart ///Widget build(BuildContext context) { @@ -153,7 +153,7 @@ class GaugeAnnotation { /// by [positionFactor]. The annotation is positioned on the basis /// of the calculated direction and distance. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart ///Widget build(BuildContext context) { diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/gauge_axis.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/gauge_axis.dart index 725042f61..a46844353 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/gauge_axis.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/gauge_axis.dart @@ -112,7 +112,7 @@ abstract class GaugeAxis { /// /// The range of the axis scale is starting from this value. /// - /// Defaults to 0 + /// Defaults to `0`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -129,7 +129,7 @@ abstract class GaugeAxis { /// /// The range of the axis scale is end with this value. /// - /// Defaults to 100 + /// Defaults to `100`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -149,7 +149,7 @@ abstract class GaugeAxis { /// interval will be measured automatically based on scale range /// along with the available width. /// - /// Defaults to null + /// Defaults to `null`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -164,7 +164,7 @@ abstract class GaugeAxis { /// Add minor ticks count per interval. /// - /// Defaults to 1 + /// Defaults to `1`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -179,7 +179,7 @@ abstract class GaugeAxis { /// Whether to show the labels on the axis of the gauge. /// - /// Defaults to true + /// Defaults to `true`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -194,7 +194,7 @@ abstract class GaugeAxis { /// Whether to show the axis line of the gauge. /// - /// Defaults to true + /// Defaults to `true`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -209,7 +209,7 @@ abstract class GaugeAxis { /// Whether to show the ticks on the axis of the gauge. /// - /// Defaults to true + /// Defaults to `true`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -235,7 +235,7 @@ abstract class GaugeAxis { /// If [offsetUnit] is [GaugeSizeUnit.logicalPixel], the defined value /// distance ticks will move from the axis line. /// - /// Defaults to 0 and [offsetUnit] is [GaugeSizeUnit.logicalPixel]. + /// Defaults to `0` and [offsetUnit] is `GaugeSizeUnit.logicalPixel`. /// /// ```dart /// Widget build(BuildContext context) { @@ -262,7 +262,7 @@ abstract class GaugeAxis { /// If [offsetUnit] is [GaugeSizeUnit.logicalPixel], the defined value /// distance labels will move from the end of the tick. /// - /// Defaults to 15 and [offsetUnit] is [GaugeSizeUnit.logicalPixel]. + /// Defaults to `15` and [offsetUnit] is `GaugeSizeUnit.logicalPixel`. ///```dart /// Widget build(BuildContext context) { /// return Container( @@ -280,7 +280,7 @@ abstract class GaugeAxis { /// Axis is rendered by default in the clockwise direction and can be /// inverted to render the axis element in the counter clockwise direction. /// - /// Defaults to false + /// Defaults to `false`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -296,7 +296,7 @@ abstract class GaugeAxis { /// The maximum number of labels to be displayed in 100 logical /// pixels on the axis. /// - /// Defaults to 3 + /// Defaults to `3`. ///```dart /// Widget build(BuildContext context) { /// return Container( @@ -311,7 +311,7 @@ abstract class GaugeAxis { /// Whether to use the range color for axis elements such as labels and ticks. /// - /// Defaults to false + /// Defaults to `false`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -332,7 +332,7 @@ abstract class GaugeAxis { /// The labels can be customized by adding the desired text as prefix /// or suffix. /// - /// Defaults to null + /// Defaults to `null`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -347,7 +347,7 @@ abstract class GaugeAxis { /// Formats the axis labels with globalized label formats. /// - /// Defaults to null. + /// Defaults to `null`. /// /// Also refer [NumberFormat]. /// @@ -370,7 +370,7 @@ abstract class GaugeAxis { /// If [ElementsPosition.outside], the position of the tick is outside the /// axis line. /// - /// Defaults to [ElementsPosition.inside]. + /// Defaults to `ElementsPosition.inside`. /// /// Also refer [ElementsPosition]. /// @@ -393,7 +393,7 @@ abstract class GaugeAxis { /// If [ElementsPosition.outside], the position of the labels is outside the /// axis line. /// - /// Defaults to [ElementsPosition.inside] + /// Defaults to `ElementsPosition.inside`. /// /// Also refer [ElementsPosition] /// @@ -413,8 +413,8 @@ abstract class GaugeAxis { /// /// Using [GaugeTextStyle] to add the style to the axis labels. /// - /// Defaults to the [GaugeTextStyle] property with font size is 12.0 and - /// font family is Segoe UI. + /// Defaults to the [GaugeTextStyle] property with font size `12.0` and + /// font family `Segoe UI`. /// /// Also refer [GaugeTextStyle] /// @@ -435,8 +435,8 @@ abstract class GaugeAxis { /// /// Using [AxisLineStyle] to add the customized style to the axis line. /// - /// Defaults to the [AxisLineStyle] property with thickness is 10 - /// logical pixels. + /// Defaults to the [AxisLineStyle] property with thickness `10 + /// logical pixels`. /// /// Also refer [AxisLineStyle] /// @@ -458,8 +458,8 @@ abstract class GaugeAxis { /// Using [MajorTickStyle] to add the customized style to the axis /// major tick line. /// - /// Defaults to the [MajorTickStyle] property with length is 10 logical - /// pixels. + /// Defaults to the [MajorTickStyle] property with length `10 logical + /// pixels`. /// /// Also refer [MajorTickStyle] /// @@ -482,8 +482,8 @@ abstract class GaugeAxis { /// Using [MinorTickStyle] to add the customized style to the axis minor tick /// line. /// - /// Defaults to the [MinorTickStyle] property with length is 5 - /// logical pixels. + /// Defaults to the [MinorTickStyle] property with length `5 + /// logical pixels`. /// /// Also refer [MinorTickStyle]. /// @@ -505,7 +505,7 @@ abstract class GaugeAxis { /// /// Using [GaugeSizeUnit], axis ticks and label position is calculated. /// - /// Defaults to [GaugeSizeUnit.logicalPixel]. + /// Defaults to `GaugeSizeUnit.logicalPixel`. ///```dart /// Widget build(BuildContext context) { /// return Container( 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 44411ceab..e8c14aa75 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 @@ -252,10 +252,10 @@ class RadialAxis extends GaugeAxis { /// ``` final bool showLastLabel; - /// The callback that is called when an axis label is created. + /// Callback that gets triggered when an axis label is created. /// /// The [RadialAxis] passes the [AxisLabelCreatedArgs] to the callback - /// is used to change text value and rotate the + /// used to change text value and rotate the /// text based on angle. /// ```dart /// Widget build(BuildContext context) { @@ -276,9 +276,9 @@ class RadialAxis extends GaugeAxis { /// ``` final ValueChanged onLabelCreated; - /// The callback that is called when an axis is tapped. + /// Callback, which is triggered by tapping an axis. /// - /// The [RadialAxis] passes the tapped axis value to the callback. + /// [RadialAxis] returns the tapped axis value to the callback. /// /// ``` dart /// Widget build(BuildContext context) { diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/common.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/common.dart index 2f7842bfa..fdebe69c8 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/common.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/common.dart @@ -15,7 +15,9 @@ typedef MarkerPointerRendererFactory typedef NeedlePointerRendererFactory = NeedlePointerRenderer Function(); -/// A style in which paint text. +/// This class has the property of the guage text style. +/// +/// Provides the options of color, font family, font style, font size, and font-weight to customize the appearance. class GaugeTextStyle { /// Creates a gauge text style with default or required properties. GaugeTextStyle( @@ -25,19 +27,25 @@ class GaugeTextStyle { this.fontWeight = FontWeight.normal, this.fontSize = 12}); - /// The color to use when painting the text. + /// To set the color of guage text. Color color; - /// The name of the font to use when painting the text (e.g., Roboto) + /// To set the font family to guage text. + /// + ///Defaults to `Roboto`. String fontFamily; - /// The typeface variant to use when drawing the text (e.g., italics) + /// To set the font style to guage text. FontStyle fontStyle; - /// The typeface thickness to use when painting the text (e.g., bold). + /// To set the font weight to guage text. + /// + /// Defaults to FontWeight.normal FontWeight fontWeight; - /// The size of glyphs (in logical pixels) to use when painting the text. + /// To set the font size to guage text + /// + /// Defaults to `12`. double fontSize; @override @@ -127,8 +135,8 @@ class GaugeTitle { /// /// Using [TextStyle] to add the style to the axis labels. /// - /// Defaults to the [TextStyle] property with font size is 12.0 - /// and font family is Segoe UI. + /// Defaults to the [TextStyle] property with font size `12.0` + /// and font family `Segoe UI`. /// /// Also refer [TextStyle] /// @@ -154,7 +162,7 @@ class GaugeTitle { /// /// Changing the background color will cause the gauge title to the new color. /// - /// Defaults to null + /// Defaults to `null`. /// ///```dart ///Widget build(BuildContext context) { @@ -171,7 +179,7 @@ class GaugeTitle { /// The color that fills the border with the title of the gauge. /// - /// Defaults to null + /// Defaults to `null`. ///```dart /// Widget build(BuildContext context) { /// return Container( @@ -188,7 +196,7 @@ class GaugeTitle { /// Specifies the border width of the gauge title. /// - /// Defaults to 0 + /// Defaults to `0`. ///```dart ///Widget build(BuildContext context) { /// return Container( @@ -213,7 +221,7 @@ class GaugeTitle { /// of gauge and the [GaugeAlignment.far] places the gauge title at the /// end of the gauge. /// - /// Defaults to [GaugeAlignment.center] + /// Defaults to `GaugeAlignment.center`. /// /// Also refer [GaugeAlignment] /// @@ -299,7 +307,7 @@ class MajorTickStyle { /// tick length is 20(0.2 * 100) logical pixels. if [lengthUnit] is /// [GaugeSizeUnit.logicalPixel], defined value is set to the tick length. /// - /// Defaults to 7 and [lengthUnit] is [GaugeSizeUnit.logicalPixel] + /// Defaults to `7` and [lengthUnit] is `GaugeSizeUnit.logicalPixel`. /// /// ``` dart ///Widget build(BuildContext context) { @@ -317,7 +325,7 @@ class MajorTickStyle { /// /// Using [GaugeSizeUnit], tick length size is calculated. /// - /// Defaults to [GaugeSizeUnit.logicalPixel] + /// Defaults to `GaugeSizeUnit.logicalPixel`. /// /// Also refer [GaugeSizeUnit] /// @@ -336,7 +344,7 @@ class MajorTickStyle { /// Specifies the thickness of tick in logical pixels. /// - /// Defaults to 1.5 + /// Defaults to `1.5`. /// /// ```dart ///Widget build(BuildContext context) { @@ -352,7 +360,7 @@ class MajorTickStyle { /// Specifies the color of tick. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart ///Widget build(BuildContext context) { @@ -368,7 +376,7 @@ class MajorTickStyle { /// Specifies the dash array to draw the dashed line. /// - /// Defaults to null. + /// Defaults to `null`. /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -481,7 +489,7 @@ class AxisLineStyle { /// /// Using [GaugeSizeUnit], axis line thickness is calculated. /// - /// Defaults to [GaugeSizeUnit.logicalPixel]. + /// Defaults to `GaugeSizeUnit.logicalPixel`. /// /// Also refer [GaugeSizeUnit]. /// @@ -511,7 +519,7 @@ class AxisLineStyle { /// If [thicknessUnit] is [GaugeSizeUnit.logicalPixel], the defined value /// for axis line thickness is set. /// - /// Defaults to 10 and [thicknessUnit] is [GaugeSizeUnit.logicalPixel]. + /// Defaults to `10` and [thicknessUnit] is `GaugeSizeUnit.logicalPixel`. /// /// ```dart ///Widget build(BuildContext context) { @@ -527,7 +535,7 @@ class AxisLineStyle { /// Specifies the color of axis line. /// - /// Defaults to null + /// Defaults to `null`. /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -547,7 +555,7 @@ class AxisLineStyle { /// * [CornerStyle.startCurve] renders the rounded corner on start side. /// * [CornerStyle.endCurve] renders the rounded corner on end side. /// - /// Defaults to [CornerStyle.bothFlat]. + /// Defaults to `CornerStyle.bothFlat`. /// /// Also refer [CornerStyle]. /// @@ -565,7 +573,7 @@ class AxisLineStyle { /// Specifies the dash array for axis line to draw the dashed line. /// - /// Defaults to null + /// Defaults to `null`. /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -583,7 +591,7 @@ class AxisLineStyle { /// [gradient] of [AxisLineStyle] only support [SweepGradient] and /// specified [SweepGradient.stops] are applied within the axis range value. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart ///Widget build(BuildContext context) { @@ -640,13 +648,13 @@ class CircularAxisLabel { CircularAxisLabel( this.labelStyle, this.text, this.index, this._needsRotateLabel); - /// The style to use for the axis label text. + /// Style for axis label text. GaugeTextStyle labelStyle; /// Holds the size of axis label Size labelSize; - /// The text to display + /// Text to display String text; /// Specifies the axis index position @@ -712,7 +720,7 @@ class ValueChangingArgs { bool cancel; } -/// A style in which draw pointer tail. +/// Style for drawing pointer's tail. /// /// ```dart ///Widget build(BuildContext context) { @@ -745,7 +753,7 @@ class TailStyle { /// Specifies the color of the tail. /// - /// Defaults to null + /// Defaults to `null`. ///```dart ///Widget build(BuildContext context) { /// return Container( @@ -762,7 +770,7 @@ class TailStyle { /// Specifies the width of the tail. /// - /// Defaults to 0 + /// Defaults to `0`. /// ``` dart ///Widget build(BuildContext context) { /// return Container( @@ -789,7 +797,7 @@ class TailStyle { /// if [lengthUnit] is [GaugeSizeUnit.logicalPixel], defined value length /// from axis center. /// - /// Defaults to 0 and [lengthUnit] is [GaugeSizeUnit.factor]. + /// Defaults to `0` and [lengthUnit] is `GaugeSizeUnit.factor`. /// /// ``` dart ///Widget build(BuildContext context) { @@ -810,7 +818,7 @@ class TailStyle { /// /// Using [GaugeSizeUnit], pointer tail length is calculated. /// - /// Defaults to [GaugeSizeUnit.factor]. + /// Defaults to `GaugeSizeUnit.factor`. /// /// Also refer [GaugeSizeUnit] /// @@ -831,7 +839,7 @@ class TailStyle { /// Specifies the border width of tail. /// - /// Defaults to 0 + /// Defaults to `0`. /// /// ``` dart ///Widget build(BuildContext context) { @@ -850,7 +858,7 @@ class TailStyle { /// Specifies the border color of tail. /// - /// Defaults to null + /// Defaults to `null`. /// ``` dart ///Widget build(BuildContext context) { /// return Container( @@ -871,7 +879,7 @@ class TailStyle { /// [gradient] of [TailStyle] only support [LinearGradient]. /// You can use this to display the depth effect of the needle pointer. /// - /// Defaults to null + /// Defaults to `null`. /// /// ``` dart ///Widget build(BuildContext context) { @@ -971,7 +979,7 @@ class KnobStyle { /// if [sizeUnit] is [GaugeSizeUnit.logicalPixel], defined value is /// set to the knob radius. /// - /// Defaults to 0.08 and [sizeUnit] is [GaugeSizeUnit.factor]. + /// Defaults to 0.08 and `sizeUnit` is `GaugeSizeUnit.factor`. /// /// ```dart ///Widget build(BuildContext context) { @@ -990,7 +998,7 @@ class KnobStyle { /// /// Using [GaugeSizeUnit], pointer knob radius size is calculated. /// - /// Defaults to [GaugeSizeUnit.factor]. + /// Defaults to `GaugeSizeUnit.factor`. /// /// Also refer [GaugeSizeUnit] /// @@ -1011,7 +1019,7 @@ class KnobStyle { /// Specifies the knob border width in logical pixel. /// - /// Defaults to 0 + /// Defaults to `0`. /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -1029,7 +1037,7 @@ class KnobStyle { /// Specifies the knob color. /// - /// Defaults to null + /// Defaults to `null`. /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -1046,7 +1054,7 @@ class KnobStyle { /// Specifies the knob border color. /// - /// Defaults to null + /// Defaults to `null`. /// ```dart ///Widget build(BuildContext context) { /// return Container( diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/radial_gauge_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/radial_gauge_renderer.dart index 2b631299d..94e48630b 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/radial_gauge_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/radial_gauge_renderer.dart @@ -22,8 +22,6 @@ class _AxisContainer extends StatelessWidget { final Offset tapPosition = renderBox.globalToLocal(details.globalPosition); for (num i = 0; i < _gauge.axes.length; i++) { - final RadialAxisRenderer axisRenderer = - _renderingDetails.axisRenderers[i]; final List<_GaugePointerRenderer> _pointerRenderers = _renderingDetails.gaugePointerRenderers[i]; if (_gauge.axes[i].pointers != null && @@ -32,37 +30,8 @@ class _AxisContainer extends StatelessWidget { final GaugePointer pointer = _gauge.axes[i].pointers[j]; final _GaugePointerRenderer pointerRenderer = _pointerRenderers[j]; if (pointer.enableDragging && pointerRenderer._isDragStarted) { - final Rect rect = Rect.fromLTRB( - axisRenderer._axisRect.left + axisRenderer._axisCenter.dx, - axisRenderer._axisRect.top + axisRenderer._axisCenter.dy, - axisRenderer._axisRect.right + axisRenderer._axisCenter.dx, - axisRenderer._axisRect.bottom + axisRenderer._axisCenter.dy); - if (pointer is RangePointer) { - final double actualCenterX = pointerRenderer._pointerRect.left + - axisRenderer._axisCenter.dx + - axisRenderer._radius; - final double actualCenterY = pointerRenderer._pointerRect.top + - axisRenderer._axisCenter.dy + - axisRenderer._radius; - final double x = tapPosition.dx - actualCenterX; - final double y = tapPosition.dy - actualCenterY; - - /// Checks whether the tapped position is available inside the - /// radius of range pointer - final bool isInside = (x * x) + (y * y) <= - (axisRenderer._radius * axisRenderer._radius); - if (isInside) { - pointerRenderer._updateDragValue( - tapPosition.dx, tapPosition.dy, _renderingDetails); - } - } - - /// Checks whether the tapped position is available inside the - /// rect of needle or marker pointer - else if (rect.contains(tapPosition)) { - pointerRenderer._updateDragValue( - tapPosition.dx, tapPosition.dy, _renderingDetails); - } + pointerRenderer._updateDragValue( + tapPosition.dx, tapPosition.dy, _renderingDetails); } } } @@ -209,6 +178,12 @@ class _AxisContainer extends StatelessWidget { renderer._isDragStarted = false; } } else { + Rect pointerRect = pointerRenderer._pointerRect; + pointerRect = Rect.fromLTRB( + pointerRect.left - 20, + pointerRect.top - 20, + pointerRect.right - 20, + pointerRect.bottom - 20); if (pointerRenderer._pointerRect.contains(tapPosition)) { pointerRenderer._isDragStarted = true; pointerRenderer._createPointerValueChangeStartArgs(); 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 eedc1e159..0e311c49f 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 @@ -61,7 +61,7 @@ class SfRadialGauge extends StatefulWidget { /// To specify title description to [GaugeTitle.text] property and /// title style to [GaugeTitle.textStyle] property. /// - /// Defaults to null. + /// Defaults to `null`.. /// /// Also refer [GaugeTitle]. /// diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/gauge_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/gauge_pointer.dart index 4db5b7f69..1507ed9ed 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/gauge_pointer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/gauge_pointer.dart @@ -19,7 +19,7 @@ abstract class GaugePointer { /// Changing the pointer value will cause the pointer to animate to the /// new value. /// - /// Defaults to 0 + /// Defaults to `0`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -38,7 +38,7 @@ abstract class GaugePointer { /// It provides an option to drag a pointer from one value to another. /// It is used to change the value at run time. /// - /// Defaults to false + /// Defaults to `false`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -178,7 +178,7 @@ abstract class GaugePointer { /// to animate to the new value. /// The animation duration is specified by [animationDuration]. /// - /// Defaults to false + /// Defaults to `false`. /// /// ```dart /// Widget build(BuildContext context) { @@ -197,7 +197,7 @@ abstract class GaugePointer { /// /// Duration is defined in milliseconds. /// - /// Defaults to 1000 + /// Defaults to `1000`. /// /// ```dart /// Widget build(BuildContext context) { @@ -217,7 +217,7 @@ abstract class GaugePointer { /// Different type of animation provides visually appealing way /// when the pointer moves from one value to another. /// - /// Defaults to [AnimationType.linear] + /// Defaults to `AnimationType.linear`. /// /// Also refer [AnimationType] /// @@ -333,6 +333,7 @@ abstract class _GaugePointerRenderer { // Restricts the dragging of pointer once the maximum value of axis // is reached if (_axisRenderer._sweepAngle != 360 && + niceInterval != _axis.maximum / 2 && ((actualValue.round() <= niceInterval && _currentValue >= _axis.maximum - niceInterval) || (actualValue.round() >= _axis.maximum - niceInterval && 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 44a2d0f05..cd5d0cc96 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 @@ -99,7 +99,7 @@ class MarkerPointer extends GaugePointer { /// Specifies the color for marker pointer. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart /// Widget build(BuildContext context) { @@ -154,7 +154,7 @@ class MarkerPointer extends GaugePointer { /// /// [imageUrl] is required when setting [MarkerType.image]. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart /// Widget build(BuildContext context) { @@ -175,7 +175,7 @@ class MarkerPointer extends GaugePointer { /// /// [text] is required when setting [MarkerType.text]. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart /// Widget build(BuildContext context) { @@ -199,7 +199,7 @@ class MarkerPointer extends GaugePointer { /// Defaults to the [GaugeTextStyle] property with font size is 12.0 and /// font family is Segoe UI. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart /// Widget build(BuildContext context) { @@ -277,7 +277,7 @@ class MarkerPointer extends GaugePointer { /// [MarkerType.diamond], [MarkerType.invertedTriangle], /// [MarkerType.triangle] and [MarkerType.rectangle]. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart /// Widget build(BuildContext context) { 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 cfaa989f0..fca5b2b1b 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 @@ -86,7 +86,7 @@ class NeedlePointer extends GaugePointer { /// The style to use for the needle tail. /// - /// Defaults to null. + /// Defaults to `null`.. /// /// Also refer [TailStyle]. /// @@ -194,7 +194,7 @@ class NeedlePointer extends GaugePointer { /// Specifies the color of the needle pointer. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart /// Widget build(BuildContext context) { @@ -214,7 +214,7 @@ class NeedlePointer extends GaugePointer { /// [gradient] of [NeedlePointer] only support [LinearGradient]. You can use /// this to display the depth effect of the needle pointer. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart /// Widget build(BuildContext context) { 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 6b7f34c5b..5aa3ae58f 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 @@ -73,7 +73,7 @@ class RangePointer extends GaugePointer { /// When you specify [pointerOffset] is negative, the range pointer will be /// positioned outside the axis. /// - /// Defaults to 0 and [sizeUnit] is [GaugeSizeUnit.logicalPixel] + /// Defaults to `0` and [sizeUnit] is `GaugeSizeUnit.logicalPixel`. /// /// ```dart /// Widget build(BuildContext context) { @@ -93,7 +93,7 @@ class RangePointer extends GaugePointer { /// /// Using [GaugeSizeUnit], range pointer position is calculated. /// - /// Defaults to [GaugeSizeUnit.logicalPixel]. + /// Defaults to `GaugeSizeUnit.logicalPixel`. /// /// Also refer [GaugeSizeUnit]. /// @@ -124,7 +124,7 @@ class RangePointer extends GaugePointer { /// if [sizeUnit] is [GaugeSizeUnit.logicalPixel], defined value is set /// to the pointer width. /// - /// Defaults to 10 and [sizeUnit] is [GaugeSizeUnit.logicalPixel] + /// Defaults to `10` and [sizeUnit] is `GaugeSizeUnit.logicalPixel`. /// /// ```Dart /// Widget build(BuildContext context) { @@ -140,7 +140,7 @@ class RangePointer extends GaugePointer { /// Specifies the range pointer color. /// - /// Defaults to null + /// Defaults to `null`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -161,7 +161,7 @@ class RangePointer extends GaugePointer { /// [CornerStyle.startCurve] renders the rounded corner on start side. /// [CornerStyle.endCurve] renders the rounded corner on end side. /// - /// Defaults to [CornerStyle.bothFlat] + /// Defaults to `CornerStyle.bothFlat`. /// /// Also refer [CornerStyle] /// @@ -183,7 +183,7 @@ class RangePointer extends GaugePointer { /// [gradient] of [RangePointer] only support [SweepGradient] and /// specified [SweepGradient.stops] are applied within the pointer value. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart /// Widget build(BuildContext context) { @@ -204,7 +204,7 @@ class RangePointer extends GaugePointer { /// Specifies the dash array to draw the dashed line. /// - /// Defaults to null. + /// Defaults to `null`. /// ```dart ///Widget build(BuildContext context) { /// return Container( 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 27f7095e5..0f58bc44e 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 @@ -60,7 +60,7 @@ class GaugeRange { /// /// The [startValue] must be greater than the minimum value of the axis. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart /// Widget build(BuildContext context) { @@ -81,7 +81,7 @@ class GaugeRange { /// /// The [endValue] must be less than the maximum value of the axis. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart ///Widget build(BuildContext context) { @@ -108,7 +108,7 @@ class GaugeRange { /// if [sizeUnit] is [GaugeSizeUnit.logicalPixel], the defined value is set /// for the start width of the range. /// - /// Defaults to 10 and [sizeUnit] is [GaugeSizeUnit.logicalPixel] + /// Defaults to `10` and [sizeUnit] is `GaugeSizeUnit.logicalPixel`. /// /// ```dart ///Widget build(BuildContext context) { @@ -135,7 +135,7 @@ class GaugeRange { /// If [sizeUnit] is [GaugeSizeUnit.logicalPixel], the defined value is set /// for the end width of the range. /// - /// Defaults to 10 and [sizeUnit] is [GaugeSizeUnit.logicalPixel] + /// Defaults to `10` and [sizeUnit] is `GaugeSizeUnit.logicalPixel`. /// /// ```dart ///Widget build(BuildContext context) { @@ -155,7 +155,7 @@ class GaugeRange { /// /// Using [GaugeSizeUnit], range position and size is calculated. /// - /// Defaults to [GaugeSizeUnit.logicalPixel]. + /// Defaults to `GaugeSizeUnit.logicalPixel`. /// /// Also refer [GaugeSizeUnit]. /// @@ -189,7 +189,7 @@ class GaugeRange { /// When you specify [rangeOffset] is negative, the range will be positioned /// outside the axis. /// - /// Defaults to 0 and [sizeUnit] is [GaugeSizeUnit.logicalPixel] + /// Defaults to `0` and [sizeUnit] is `GaugeSizeUnit.logicalPixel`. /// /// ```dart ///Widget build(BuildContext context) { @@ -206,7 +206,7 @@ class GaugeRange { /// Specifies the range color. /// - /// Defaults to null + /// Defaults to `null`. /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -224,8 +224,8 @@ class GaugeRange { /// /// Using [GaugeTextStyle] to add the style to the axis labels. /// - /// Defaults to the [GaugeTextStyle] property with font size is 12.0 and - /// font family is Segoe UI. + /// Defaults to the [GaugeTextStyle] property with font size `12.0` and + /// font family `Segoe UI`. /// /// Also refer [GaugeTextStyle]. /// @@ -248,7 +248,7 @@ class GaugeRange { /// /// [label] style is customized by [labelStyle]. /// - /// Defaults to null + /// Defaults to `null`. /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -267,7 +267,7 @@ class GaugeRange { /// [gradient] of [GaugeRange] only support [SweepGradient] and /// specified [SweepGradient.stops] are applied within the range value. /// - /// Defaults to null + /// Defaults to `null`. /// /// ```dart /// Widget build(BuildContext context) { diff --git a/packages/syncfusion_flutter_maps/README.md b/packages/syncfusion_flutter_maps/README.md index 53a58effe..5a77cd612 100644 --- a/packages/syncfusion_flutter_maps/README.md +++ b/packages/syncfusion_flutter_maps/README.md @@ -41,7 +41,7 @@ Create a highly interactive and customizable maps widget that has features set i ![maps selection](https://cdn.syncfusion.com/content/images/Flutter/pub_images/maps_images/map_selection.png) -**Legend** - Use legends to provide clear information on the data plotted in the map. You can use the legend toggling feature to visualize only the shapes that need to be interpreted. +**Legend** - Use legends to provide clear information on the data plotted in the map. You can use the legend toggling feature to visualize only the shapes that need to be interpreted. It is also possible to use a bar-style legend with an optional gradient background. ![maps legend](https://cdn.syncfusion.com/content/images/Flutter/pub_images/maps_images/map_legend.png) @@ -49,20 +49,38 @@ Create a highly interactive and customizable maps widget that has features set i ![color mapping](https://cdn.syncfusion.com/content/images/Flutter/pub_images/maps_images/color_mapping.png) -**Tooltip** - Display additional information about the shapes and bubbles using a customizable tooltip on a map. +**Tooltip** - Display additional information about the shapes, bubbles, and markers using a customizable tooltip on a map. ![maps tooltip](https://cdn.syncfusion.com/content/images/Flutter/pub_images/maps_images/map_tooltip.png) +**Shape sublayer** +Add a shape sublayer with GeoJSON data on another shape layer to show more details about a particular region. + +**Vector layer** +Add shapes such as polylines, lines, polygons, circles, and arcs as a sublayer in the shape layer. + **Zooming and panning** - Zoom in shape layer for a closer look at a specific region by pinching, scrolling the mouse wheel or track pad, or using the toolbar on the web. Pan the map to navigate across the regions. ![maps zoompan](https://cdn.syncfusion.com/content/images/zoompan.gif) ### Tile layer -**Markers** - Show markers for the tile layer in the specific latitudes and longitudes. +**Markers** - Show markers for the tile layer in the specific latitudes and longitudes. Display additional information about the markers using a customizable tooltip on a map. ![tile layer marker](https://cdn.syncfusion.com/content/images/Tile_withmarker.png) +**Shape sublayer** +Add a shape sublayer with GeoJSON data on the tile layer to show more details about a particular region. + +![tile layer shape sublayer](https://cdn.syncfusion.com/content/images/FTControl/tile_shapesublayer.jpg) + +**Vector layer** +Add shapes such as polylines, lines, polygons, circles, and arcs as a sublayer in the tile layer. + +![tile layer arc](https://cdn.syncfusion.com/content/images/FTControl/arc.jpg) + +![tile layer polyline](https://cdn.syncfusion.com/content/images/FTControl/polyline.jpg) + **Zooming and panning** - Zoom in tile layer for a closer look at a specific region by pinching, scrolling the mouse wheel or track pad, or using the toolbar on the web. Pan the map to navigate across the regions. ## Get the demo application @@ -115,9 +133,9 @@ Widget build(BuildContext context) { ### Add a GeoJSON file -The `layers` in `SfMaps` contains collection of `MapShapeLayer`. The actual geographical rendering is done in the each `MapShapeLayer`. The `delegate` property of the `MapShapeLayer` is of type `MapShapeLayerDelegate`. The path of the .json file which contains the GeoJSON data has to be set to the `shapeFile` property of the `MapShapeLayerDelegate`. +The `layers` in `SfMaps` contains collection of `MapShapeLayer`. The actual geographical rendering is done in the each `MapShapeLayer`. The `source` property of the `MapShapeLayer` is of type `MapShapeSource`. The path of the .json file which contains the GeoJSON data has to be set to the `shapeFile` property of the `MapShapeSource`. -The `shapeDataField` property of the `MapShapeLayerDelegate` is used to refer the unique field name in the .json file to identify each shapes. In 'Mapping the data source' section of this document, this `shapeDataField` will be used to map with respective value returned in `primaryValueMapper` from the data source. +The `shapeDataField` property of the `MapShapeSource` is used to refer the unique field name in the .json file to identify each shapes. In 'Mapping the data source' section of this document, this `shapeDataField` will be used to map with respective value returned in `primaryValueMapper` from the data source. ```dart @override @@ -126,8 +144,8 @@ Widget build(BuildContext context) { body: SfMaps( layers: [ MapShapeLayer( - delegate: const MapShapeLayerDelegate( - shapeFile: 'assets/australia.json', + source: MapShapeSource.asset( + 'assets/australia.json', shapeDataField: 'STATE_NAME', ), ), @@ -169,8 +187,8 @@ Widget build(BuildContext context) { body: SfMaps( layers: [ MapShapeLayer( - delegate: MapShapeLayerDelegate( - shapeFile: 'assets/australia.json', + source: MapShapeSource.asset( + 'assets/australia.json', shapeDataField: 'STATE_NAME', dataCount: data.length, primaryValueMapper: (int index) => data[index].state, @@ -195,11 +213,11 @@ Add the basic maps elements such as title, data labels, legend, and tooltip as s * **Title** - You can add a title to the maps to provide a quick information about the data plotted in the map using the `title` property in the `SfMaps`. -* **Data label** - You can show data labels using the `showDataLabels` property in the `MapShapeLayer` and also, it is possible to show data labels only for the particular shapes/or show custom text using the `dataLabelMapper` property in the `MapShapeLayerDelegate`. +* **Data label** - You can show data labels using the `showDataLabels` property in the `MapShapeLayer` and also, it is possible to show data labels only for the particular shapes/or show custom text using the `dataLabelMapper` property in the `MapShapeSource`. -* **Legend** - You can show legend using the `showLegend` property in the `MapShapeLayer`. The icon color of the legend is applied based on the color returned in the `shapeColorValueMapper` property in the `MapShapeLayerDelegate`. It is possible to customize the legend item's color and text using the `shapeColorMappers` property in the `MapShapeLayerDelegate`. +* **Legend** - You can show legend using the `showLegend` property in the `MapShapeLayer`. The icon color of the legend is applied based on the color returned in the `shapeColorValueMapper` property in the `MapShapeSource`. It is possible to customize the legend item's color and text using the `shapeColorMappers` property in the `MapShapeSource`. -* **Tooltip** - You can enable tooltip for the shapes using the `enableShapeTooltip` property in the `MapShapeLayer` and also, it is possible to enable tooltip only for the particular shapes/or show custom text using the `shapeTooltipTextMapper` property in the `MapShapeLayerDelegate`. +* **Tooltip** - You can enable tooltip only for the particular shapes/or show custom text using the `shapeTooltipBuilder` property in the `MapShapeLayer`. ```dart List data; @@ -225,41 +243,44 @@ void initState() { @override Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); return Scaffold( body: Container( height: 520, child: Center( child: SfMaps( - title: const MapTitle(text: 'Australia map'), + title: const MapTitle('Australia map'), layers: [ MapShapeLayer( - delegate: MapShapeLayerDelegate( - shapeFile: 'assets/australia.json', + source: MapShapeSource.asset( + 'assets/australia.json', shapeDataField: 'STATE_NAME', dataCount: data.length, primaryValueMapper: (int index) => data[index].state, dataLabelMapper: (int index) => data[index].stateCode, shapeColorValueMapper: (int index) => data[index].color, - shapeTooltipTextMapper: (int index) => data[index].stateCode, ), + legend: MapLegend(MapElement.shape), showDataLabels: true, - showLegend: true, - enableShapeTooltip: true, - tooltipSettings: MapTooltipSettings(color: Colors.grey[700], - strokeColor: Colors.white, strokeWidth: 2 - ), + shapeTooltipBuilder: (BuildContext context, int index) { + return Padding( + padding: const EdgeInsets.all(7), + child: Text(data[index].stateCode, + style: themeData.textTheme.caption + .copyWith(color: themeData.colorScheme.surface)), + ); + }, + tooltipSettings: MapTooltipSettings( + color: Colors.grey[700], + strokeColor: Colors.white, + strokeWidth: 2), strokeColor: Colors.white, strokeWidth: 0.5, dataLabelSettings: MapDataLabelSettings( textStyle: TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: - Theme - .of(context) - .textTheme - .caption - .fontSize)), + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: themeData.textTheme.caption.fontSize)), ), ], ), diff --git a/packages/syncfusion_flutter_maps/example/lib/main.dart b/packages/syncfusion_flutter_maps/example/lib/main.dart index 023fce0f1..73205dca4 100644 --- a/packages/syncfusion_flutter_maps/example/lib/main.dart +++ b/packages/syncfusion_flutter_maps/example/lib/main.dart @@ -57,27 +57,34 @@ class _MyHomePageState extends State { height: 520, child: Center( child: SfMaps( - title: const MapTitle(text: 'Australia map'), + title: const MapTitle('Australia map'), layers: [ MapShapeLayer( - delegate: MapShapeLayerDelegate( - shapeFile: 'assets/australia.json', + source: MapShapeSource.asset( + 'assets/australia.json', shapeDataField: 'STATE_NAME', dataCount: _data.length, primaryValueMapper: (int index) => _data[index].state, dataLabelMapper: (int index) => _data[index].stateCode, shapeColorValueMapper: (int index) => _data[index].color, - shapeTooltipTextMapper: (int index) => _data[index].stateCode, ), showDataLabels: true, - legendSource: MapElement.shape, - enableShapeTooltip: true, + legend: MapLegend(MapElement.shape), tooltipSettings: MapTooltipSettings( color: Colors.grey[700], strokeColor: Colors.white, strokeWidth: 2), strokeColor: Colors.white, strokeWidth: 0.5, + shapeTooltipBuilder: (BuildContext context, int index) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + _data[index].stateCode, + style: TextStyle(color: Colors.white), + ), + ); + }, dataLabelSettings: MapDataLabelSettings( textStyle: TextStyle( color: Colors.black, diff --git a/packages/syncfusion_flutter_maps/lib/maps.dart b/packages/syncfusion_flutter_maps/lib/maps.dart index ba8b353c7..4fcff55e8 100644 --- a/packages/syncfusion_flutter_maps/lib/maps.dart +++ b/packages/syncfusion_flutter_maps/lib/maps.dart @@ -1,43 +1,446 @@ -/// Syncfusion Flutter Maps is a data visualization library written natively in -/// Dart for creating beautiful and customizable maps. +library maps; + +import 'package:flutter/foundation.dart' show DiagnosticableTree; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'src/layer/layer_base.dart'; +import 'src/layer/shape_layer.dart'; + +export 'src/behavior/zoom_pan_behavior.dart' + hide Gesture, BehaviorViewRenderObjectWidget; +export 'src/controller/shape_layer_controller.dart'; +export 'src/elements/legend.dart' hide MapLayerLegend; +export 'src/elements/marker.dart' + hide ShapeLayerMarkerContainer, RenderMapMarker; +export 'src/enum.dart' hide MapLayerElement; +export 'src/layer/layer_base.dart' + hide SublayerContainer, RenderSublayerContainer; +export 'src/layer/shape_layer.dart' + hide + MapProvider, + RenderShapeLayer, + AssetMapProvider, + MemoryMapProvider, + NetworkMapProvider; +export 'src/layer/tile_layer.dart' hide MapZoomLevel; +export 'src/layer/vector_layers.dart' hide MapVectorLayer; +export 'src/settings.dart'; + +/// Title for the [SfMaps]. +/// +/// [MapTitle] can define and customize the title for the [SfMaps]. The text +/// property of the [MapTitle] is used to set the text of the title. +// +/// It also provides option for customizing text style, alignment, decoration, +/// background color, margin and padding. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfMaps( +/// title: MapTitle( +/// text : 'World map' +/// ), +/// ); +/// } +/// ``` +class MapTitle extends DiagnosticableTree { + /// Creates a [MapTitle]. + const MapTitle( + this.text, { + this.textStyle, + this.alignment, + this.decoration, + this.color, + this.margin, + this.padding, + }); + + /// Specifies the text to be displayed as map title. + /// + /// See also: + /// * [textStyle], to customize the text. + /// * [alignment], to align the title. + final String text; + + /// Customizes the style of the [text]. + /// + /// See also: + /// * [text], to set the text for the title. + final TextStyle textStyle; + + /// Specifies the position of the title. + /// + /// Defaults to `center`. + /// + /// See also: + /// * [text], to set the text for the title. + final AlignmentGeometry alignment; + + /// Customizes the appearance of the title. + /// + /// See also: + /// * [Decoration], to set the decoration for the title. + /// * [text], to set the text for the title. + final Decoration decoration; + + /// Specifies the background color of the title. + /// + /// See also: + /// * [text], to set the text for the title. + final Color color; + + /// Customizes the margin of the title. + /// + /// See also: + /// * [EdgeInsetsGeometry], to use the EdgeInsets values. + /// * [text], to set the text for the title. + final EdgeInsetsGeometry margin; + + /// Customize the space around the title [text]. + /// + /// See also: + /// * [EdgeInsetsGeometry], to use the EdgeInsets values. + /// * [text], to set the text for the title. + final EdgeInsetsGeometry padding; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is MapTitle && + other.text == text && + other.textStyle == textStyle && + other.alignment == alignment && + other.decoration == decoration && + other.color == color && + other.margin == margin && + other.padding == padding; + } + + @override + int get hashCode => hashValues( + text, textStyle, alignment, decoration, color, margin, padding); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(StringProperty('text', text)); + if (textStyle != null) { + properties.add(textStyle.toDiagnosticsNode(name: 'textStyle')); + } + properties + .add(DiagnosticsProperty('alignment', alignment)); + properties.add(DiagnosticsProperty('decoration', decoration)); + properties.add(DiagnosticsProperty('color', color)); + properties.add(DiagnosticsProperty('margin', margin)); + properties.add(DiagnosticsProperty('padding', padding)); + } +} + +/// A data visualization component that displays statistical information for a +/// geographical area. +/// +/// The [SfMaps.layers] is a collection of [MapLayer]. It contains either +/// [MapShapeLayer] and [MapTileLayer]. +/// +/// ## Shape layer +/// +/// The [MapShapeLayer] has the following elements and features, +/// +/// * The "data labels", to provide information to users about the respective +/// shape. +/// * The "markers", which denotes a location with built-in symbols and allows +/// displaying custom widgets at a specific latitude and longitude on a map. +/// * The "bubbles", which adds information to shapes such as population +/// density, number of users, and more. Bubbles can be rendered in different +/// colors and sizes based on the data values of their assigned shape. +/// * The "legend", to provide clear information on the data plotted in the map +/// through shapes and bubbles. You can use the legend toggling feature to +/// visualize only the shapes or bubbles which needs to be visualized. +/// * The "color mapping", to categorize the shapes and bubbles on a map by +/// customizing their color based on the underlying value. It is possible to set +/// the shape or bubble color for a specific value or for a range of values. +/// * The "tooltip", to display additional information about shapes and bubbles +/// using the customizable tooltip on a map. +/// * Along with this, the selection feature helps to highlight that area on a +/// map on interaction. You can use the callback for performing any action +/// during shape selection. +/// +/// The actual geographical rendering is done here using the +/// [MapShapeLayer.source]. The source which contains the GeoJSON data can be +/// set as the .json file from an asset bundle, network or from [Uint8List] as +/// bytes. +/// +/// The [MapShapeSource.shapeDataField] property is used to +/// refer the unique field name in the .json file to identify each shapes and +/// map with the respective data in the data source. /// -/// To use, import `package:syncfusion_flutter_maps/maps.dart`; +/// By default, the value specified for the +/// [MapShapeSource.shapeDataField] in the GeoJSON file will be used in +/// the elements like data labels, tooltip, and legend for their respective +/// shapes. /// +/// However, it is possible to keep a data source and customize these elements +/// based on the requirement. The value of the +/// [MapShapeSource.shapeDataField] will be used to map with the +/// respective data returned in [MapShapeSource.primaryValueMapper] +/// from the data source. +/// +/// Once the above mapping is done, you can customize the elements using the +/// APIs like [MapShapeSource.dataLabelMapper], +/// [MapShapeSource.shapeColorMappers], etc. +/// +/// ## Example +/// +/// This snippet shows how to create the [SfMaps]. +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return Container( +/// child: const SfMaps( +/// layers: [ +/// const MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent"), +/// ) +/// ], +/// )); +/// } +/// ``` +/// +/// ## Tile layer +/// +/// Tile layer which renders the tiles returned from the Web Map Tile +/// Services (WMTS) like OpenStreetMap, Bing Maps, Google Maps, TomTom etc. +/// +/// The [urlTemplate] accepts the URL in WMTS format i.e. {z} — zoom level, {x} +/// and {y} — tile coordinates. +/// +/// This URL might vary slightly depends on the providers. The formats can be, +/// https://exampleprovider/{z}/{x}/{y}.png, +/// https://exampleprovider/z={z}/x={x}/y={y}.png, +/// https://exampleprovider/z={z}/x={x}/y={y}.png?key=subscription_key, etc. +/// +/// We will replace the {z}, {x}, {y} internally based on the +/// current center point and the zoom level. +/// +/// The subscription key may be needed for some of the providers. Please include +/// them in the [urlTemplate] itself as mentioned in above example. Please note +/// that the format may vary between the each map provider. You can check the +/// exact URL format needed for the providers in their official websites. +/// +/// Regarding the tile rendering, at the lowest zoom level (Level 0), the map is +/// 256 x 256 pixels and the whole world map renders as a single tile. At each +/// increase in level, the map width and height grow by a factor of 2 i.e. Level +/// 1 is 512 x 512 pixels with 4 tiles ((0, 0), (0, 1), (1, 0), (1, 1) where 0 +/// and 1 are {x} and {y} in [MapTileLayer.urlTemplate]), Level 2 is 2048 x 2048 +/// pixels with 8 tiles (from (0, 0) to (3, 3)), and so on. +/// (These details are just for your information and all these calculation are +/// done internally.) +/// +/// However, based on the size of the [SfMaps] widget, [initialFocalLatLng] and +/// [initialZoomLevel], number of initial tiles needed in the view port alone +/// will be rendered. While zooming and panning, new tiles will be requested and +/// rendered on demand based on the current zoom level and focal point. +/// The current zoom level and focal point can be obtained from the +/// [MapZoomPanBehavior.zoomLevel] and [MapZoomPanBehavior.focalLatLng] +/// respectively. Once the particular tile is rendered, it will be stored in the +/// cache and it will be used from it for the next time for better performance. +/// +/// Regarding the cache and clearing it, please check the APIs in +/// [imageCache](https://api.flutter.dev/flutter/painting/imageCache.html). +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfMaps( +/// layers: [ +/// MapTileLayer( +/// urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', +/// initialFocalLatLng: MapLatLng(-23.698042, 133.880753), +/// initialZoomLevel: 3 +/// ), +/// ], +/// ); +/// } +/// ``` /// See also: -/// * [Syncfusion Flutter Maps product page](https://www.syncfusion.com/flutter-widgets/flutter-maps) -/// * [User guide documentation](https://help.syncfusion.com/flutter/maps/overview) +/// * [MapShapeLayer], for enabling the features like data labels, tooltip, +/// bubbles, legends, selection etc. +/// * [MapShapeSource], for providing the data for the elements like data +/// labels, tooltip, bubbles, legends etc. +/// * For enabling zooming and panning, set [MapShapeLayer.zoomPanBehavior] or +/// [MapTileLayer.zoomPanBehavior] with the instance of [MapZoomPanBehavior]. +class SfMaps extends StatefulWidget { + /// Creates a [SfMaps]. + const SfMaps({ + Key key, + this.title, + this.layers, + }) : super(key: key); -library maps; + /// Title for the [SfMaps]. + /// + /// It can define and customize the title for the [SfMaps]. The text + /// property of the [MapTitle] is used to set the text of the title. + /// + /// It also provides option for customizing text style, alignment, decoration, + /// background color, margin and padding of the title. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// title: MapTitle( + /// text : 'World map' + /// ), + /// ); + /// } + /// ``` + final MapTitle title; -import 'dart:async'; -import 'dart:convert'; -import 'dart:math'; -import 'dart:ui'; + /// The collection of map shape layer in which geographical rendering is done. + /// + /// The snippet below shows how to render the basic world map using the data + /// from .json file. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// ), + /// ) + /// ], + /// ); + /// } + /// ``` + /// See also: + /// * [MapShapeLayer.source], to provide data for the elements of the + /// [SfMaps] like data labels, bubbles, tooltip, shape colors, and legend. + final List layers; -import 'package:collection/collection.dart' show MapEquality; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/scheduler.dart' show SchedulerBinding; -import 'package:flutter/services.dart' show rootBundle; -import 'package:http/http.dart' as http; -import 'package:syncfusion_flutter_core/theme.dart'; + @override + _SfMapsState createState() => _SfMapsState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + if (title != null) { + properties.add( + title.toDiagnosticsNode(name: 'title'), + ); + } + + if (layers != null && layers.isNotEmpty) { + final _DebugPointerTree pointerTreeNode = _DebugPointerTree(layers); + properties.add(pointerTreeNode.toDiagnosticsNode(name: 'layers')); + } + } +} + +class _SfMapsState extends State { + @override + Widget build(BuildContext context) { + assert(widget.layers != null && widget.layers.isNotEmpty); + return _MapsRenderObjectWidget( + child: widget.title != null && widget.title.text != null + ? Column( + children: [ + _title(context), + Expanded( + child: LayoutBuilder(builder: + (BuildContext context, BoxConstraints constraints) { + return Stack( + alignment: Alignment.center, + children: [widget.layers.last], + ); + }), + ), + ], + ) + : Stack( + alignment: Alignment.center, + children: [widget.layers.last], + ), + ); + } + + /// Returns the title of the [SfMaps]. + Widget _title(BuildContext context) { + final SfMapsThemeData themeData = SfMapsTheme.of(context); + return Align( + alignment: widget.title.alignment ?? Alignment.center, + child: Container( + child: Text( + widget.title.text, + style: Theme.of(context) + .textTheme + .subtitle1 + .merge(widget.title.textStyle ?? themeData.titleTextStyle), + ), + color: widget.title.color, + decoration: widget.title.decoration, + margin: widget.title.margin, + padding: + widget.title.padding ?? const EdgeInsets.symmetric(vertical: 8), + ), + ); + } +} + +class _MapsRenderObjectWidget extends SingleChildRenderObjectWidget { + const _MapsRenderObjectWidget({Key key, Widget child}) + : super(key: key, child: child); + + @override + _RenderMaps createRenderObject(BuildContext context) { + return _RenderMaps(); + } +} + +class _RenderMaps extends RenderProxyBox { + @override + void performLayout() { + final double width = + constraints.hasBoundedWidth ? constraints.maxWidth : 300; + final double height = + constraints.hasBoundedHeight ? constraints.maxHeight : 300; + child.layout(BoxConstraints.loose(Size(width, height)), + parentUsesSize: true); + size = child.size; + } +} + +class _DebugPointerTree extends DiagnosticableTree { + _DebugPointerTree(this._layer); + + final List _layer; + + @override + List debugDescribeChildren() { + if (_layer != null && _layer.isNotEmpty) { + return _layer.map((MapLayer layer) { + return layer.toDiagnosticsNode(); + }).toList(); + } + return super.debugDescribeChildren(); + } -part 'src/common/enum.dart'; -part 'src/common/maps_shapes.dart'; -part 'src/common/settings.dart'; -part 'src/common/utils.dart'; -part 'src/features/maps_bubble.dart'; -part 'src/features/maps_data_label.dart'; -part 'src/features/maps_legend.dart'; -part 'src/features/maps_marker.dart'; -part 'src/features/maps_toolbar.dart'; -part 'src/features/maps_tooltip.dart'; -part 'src/layer/maps_layer.dart'; -part 'src/layer/maps_tile_layer.dart'; -part 'src/layer/shape_layer_controller.dart'; -part 'src/layer/shape_layer_render_box.dart'; -part 'src/layer/zoom_pan_behavior.dart'; -part 'src/layer/default_controller.dart'; -part 'src/maps.dart'; + @override + String toStringShort() { + return 'contains ${_layer.length} layers'; + } +} diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/zoom_pan_behavior.dart b/packages/syncfusion_flutter_maps/lib/src/behavior/zoom_pan_behavior.dart similarity index 75% rename from packages/syncfusion_flutter_maps/lib/src/layer/zoom_pan_behavior.dart rename to packages/syncfusion_flutter_maps/lib/src/behavior/zoom_pan_behavior.dart index ee5ba0c70..a655c3b5f 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/zoom_pan_behavior.dart +++ b/packages/syncfusion_flutter_maps/lib/src/behavior/zoom_pan_behavior.dart @@ -1,20 +1,37 @@ -part of maps; +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_maps/maps.dart'; + +import '../controller/default_controller.dart'; +import '../settings.dart'; +import '../utils.dart'; + +// ignore: public_member_api_docs +enum Gesture { scale, pan } /// Base class of the map behaviors. -abstract class MapBehavior { - // Used internally to notify all interaction changes. - _DefaultController _controller; +abstract class MapBehavior extends DiagnosticableTree { + MapController _controller; - /// Render box of the map behavior. - RenderBox renderBox; + /// Render box of the internal widget which handles the [MapZoomPanBehavior]. + /// + /// The size of this render box can be obtained from the size getter. + /// This is only valid after the layout phase, and should therefore only be + /// examined from paint callbacks or interaction event handlers. + RenderBox get renderBox => _renderBox; + RenderBox _renderBox; - /// Override this method to handle pointer events that hit this render object. + /// Override this method to handle pointer events that hit this render box. @mustCallSuper - void handleEvent(PointerEvent event, HitTestEntry entry) { + void handleEvent(PointerEvent event) { _controller.notifyListeners(); } - /// Paint this render object into the given context at the given offset. + /// Paints this render box into the given context at the given offset. void paint(PaintingContext context, Offset offset) { // Implement paint. } @@ -23,16 +40,16 @@ abstract class MapBehavior { /// Enables zooming and panning in [MapShapeLayer] and [MapTileLayer]. /// /// Zooming and panning will start working when the new instance of -/// [MapZoomPanBehavior] is set to [MapShapeLayer.zoomPanBehavior]. However, -/// if you need to restrict pinch zooming or panning for any specific -/// requirements, you can set the [enablePinching] and [enablePanning] -/// properties to false respectively. +/// [MapZoomPanBehavior] is set to [MapShapeLayer.zoomPanBehavior] or +/// [MapTileLayer.zoomPanBehavior]. However, if you need to restrict pinch +/// zooming or panning for any specific requirements, you can set the +/// [enablePinching] and [enablePanning] properties to false respectively. /// /// The [focalLatLng] is the focal point of the map layer based on which zooming /// happens. /// /// The default [zoomLevel] value is 1 which will show the whole map in the -/// viewport for [MapShapeLayer] and the possible bounds for the [MapTileLayer] +/// viewport for [MapShapeLayer] and the available bounds for the [MapTileLayer] /// based on the [focalLatLng] (Please check the documentation of [MapTileLayer] /// to know more details about how [zoomLevel] works in it). /// @@ -43,12 +60,15 @@ abstract class MapBehavior { /// [minZoomLevel] and [maxZoomLevel] properties respectively. The default /// values of [minZoomLevel] and [maxZoomLevel] are 0 and 15 respectively. /// However, for [MapTileLayer], the [maxZoomLevel] may slightly vary depends -/// on the providers. Kindly check the respective official website of the map +/// on the providers. Check the respective official website of the map /// tile providers to know about the maximum zoom level it supports. /// -/// Toolbar with the the options for changing the visible bound for the web -/// platform will be enabled by default. However, you can use the [showToolbar] -/// property to changes its visibility. +/// By default, there is a toolbar for the zooming operations for the web +/// platform. However, you can change its visibility using the +/// [MapZoomPanBehavior.showToolbar] property. +/// +/// [MapZoomPanBehavior] objects are expected to be long-lived, not recreated +/// with each build. /// /// The procedure and the behavior are similar for both the [MapShapeLayer] /// and [MapTileLayer]. @@ -73,8 +93,8 @@ abstract class MapBehavior { /// body: SfMaps( /// layers: [ /// MapShapeLayer( -/// delegate: MapShapeLayerDelegate( -/// shapeFile: 'assets/world_map.json', +/// source: MapShapeSource.asset( +/// 'assets/world_map.json', /// shapeDataField: 'continent', /// ), /// zoomPanBehavior: _zoomPanBehavior, @@ -95,7 +115,7 @@ class MapZoomPanBehavior extends MapBehavior { bool enablePanning = true, bool showToolbar = true, MapToolbarSettings toolbarSettings = const MapToolbarSettings(), - }) : _zoomLevel = zoomLevel, + }) : _zoomLevel = interpolateValue(zoomLevel, minZoomLevel, maxZoomLevel), _focalLatLng = focalLatLng, _minZoomLevel = minZoomLevel, _maxZoomLevel = maxZoomLevel, @@ -109,7 +129,7 @@ class MapZoomPanBehavior extends MapBehavior { /// Defaults to 1. /// /// The default [zoomLevel] value is 1 which will show the whole map in the - /// viewport for [MapShapeLayer] and the possible bounds for the + /// viewport for [MapShapeLayer] and the available bounds for the /// [MapTileLayer] based on the [focalLatLng] (Please check the documentation /// of [MapTileLayer] to know more details about how [zoomLevel] works in it). /// @@ -131,7 +151,7 @@ class MapZoomPanBehavior extends MapBehavior { /// zooming happens. /// /// The default [zoomLevel] value is 1 which will show the whole map in the - /// viewport for [MapShapeLayer] and the possible bounds for the + /// viewport for [MapShapeLayer] and the available bounds for the /// [MapTileLayer] based on the [focalLatLng] (Please check the documentation /// of [MapTileLayer] to know more details about how [zoomLevel] works in it). MapLatLng get focalLatLng => _focalLatLng; @@ -155,6 +175,7 @@ class MapZoomPanBehavior extends MapBehavior { return; } _minZoomLevel = value; + zoomLevel = interpolateValue(zoomLevel, _minZoomLevel, _maxZoomLevel); } /// Maximum zoom level of the map layer. @@ -162,7 +183,7 @@ class MapZoomPanBehavior extends MapBehavior { /// Defaults to 15. /// /// However, for [MapTileLayer], the [maxZoomLevel] may slightly vary depends - /// on the providers. Kindly check the respective official website of the map + /// on the providers. Check the respective official website of the map /// tile providers to know about the maximum zoom level it supports. double get maxZoomLevel => _maxZoomLevel; double _maxZoomLevel; @@ -171,6 +192,7 @@ class MapZoomPanBehavior extends MapBehavior { return; } _maxZoomLevel = value; + zoomLevel = interpolateValue(zoomLevel, _minZoomLevel, _maxZoomLevel); } /// Option to enable pinch zooming support. @@ -204,7 +226,7 @@ class MapZoomPanBehavior extends MapBehavior { /// /// Defaults to `true` in web platform. /// - /// However, you can use this property to changes its visibility. + /// However, you can use this property to change its visibility. bool get showToolbar => _showToolbar; bool _showToolbar; set showToolbar(bool value) { @@ -214,8 +236,8 @@ class MapZoomPanBehavior extends MapBehavior { _showToolbar = value; } - /// Provides options for customizing the appearance of the toolbar in thw web - /// platform. + /// Provides options for customizing the appearance of the toolbar + /// in the web platform. MapToolbarSettings get toolbarSettings => _toolbarSettings; MapToolbarSettings _toolbarSettings; set toolbarSettings(MapToolbarSettings value) { @@ -291,6 +313,34 @@ class MapZoomPanBehavior extends MapBehavior { _controller.notifyResetListeners(); _controller.notifyListeners(); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + + properties.add(DoubleProperty('zoomLevel', zoomLevel)); + properties.add(DoubleProperty('minZoomLevel', minZoomLevel)); + properties.add(DoubleProperty('maxZoomLevel', maxZoomLevel)); + properties.add(FlagProperty('enablePanning', + value: enablePanning, + ifTrue: 'Panning is enabled', + ifFalse: 'Panning is disabled', + showName: false)); + properties.add(FlagProperty('enablePinching', + value: enablePinching, + ifTrue: 'Pinching is enabled', + ifFalse: 'Pinching is disabled', + showName: false)); + properties.add(DiagnosticsProperty('focalLatLng', focalLatLng)); + properties.add(FlagProperty('showToolbar', + value: showToolbar, + ifTrue: 'Toolbar is enabled', + ifFalse: 'Toolbar is disabled', + showName: false)); + properties.add( + toolbarSettings.toDiagnosticsNode(name: 'toolbarSettings'), + ); + } } /// Latitude and longitude in the maps. @@ -322,6 +372,19 @@ class MapLatLng { other.longitude == longitude; } + /// Linearely interpolating between two latlngs. + /// + /// The arguments must not be null. + static MapLatLng lerp(MapLatLng a, MapLatLng b, double t) { + assert(t != null); + if (a == null && b == null) { + return null; + } + + return MapLatLng(a.latitude + (b.latitude - a.latitude) * t, + a.longitude + (b.longitude - a.longitude) * t); + } + @override int get hashCode => hashValues(latitude, longitude); @@ -336,10 +399,10 @@ class MapLatLngBounds { /// Creates a [MapLatLngBounds]. const MapLatLngBounds(this.northeast, this.southwest); - /// The north east [MapLatLng]. + /// The northeast [MapLatLng] bound. final MapLatLng northeast; - /// The southwest [MapLatLng]. + /// The southwest [MapLatLng] bound. final MapLatLng southwest; @override @@ -401,8 +464,6 @@ class MapZoomDetails { /// Hence, if the `super.onZooming(details)` is not called, there will be no /// changes in the UI. final MapLatLngBounds newVisibleBounds; - - MapLatLng _newFocalLatLng; } /// Contains details about the current pan position. @@ -438,25 +499,28 @@ class MapPanDetails { /// The local focal point of the pointers in contact with the screen. final Offset localFocalPoint; - - MapLatLng _newFocalLatLng; } -class _BehaviorViewRenderObjectWidget extends LeafRenderObjectWidget { - const _BehaviorViewRenderObjectWidget({ +/// Render object widget of the internal widget which handles +/// the [MapZoomPanBehavior]. +class BehaviorViewRenderObjectWidget extends LeafRenderObjectWidget { + /// Creates [BehaviorViewRenderObjectWidget]. + const BehaviorViewRenderObjectWidget({ Key key, - this.defaultController, + this.controller, this.zoomPanBehavior, }) : super(key: key); - final _DefaultController defaultController; + /// Used to coordinate with [MapShapeLayer] and its elements. + final MapController controller; + /// Enables zooming and panning in [MapShapeLayer] and [MapTileLayer]. final MapZoomPanBehavior zoomPanBehavior; @override RenderObject createRenderObject(BuildContext context) { return _RenderBehaviorView( - listener: defaultController, + listener: controller, zoomPanBehavior: zoomPanBehavior, ); } @@ -465,21 +529,22 @@ class _BehaviorViewRenderObjectWidget extends LeafRenderObjectWidget { void updateRenderObject( BuildContext context, _RenderBehaviorView renderObject) { renderObject - ..defaultController = defaultController + ..controller = controller ..zoomPanBehavior = zoomPanBehavior; } } class _RenderBehaviorView extends RenderBox { _RenderBehaviorView({ - _DefaultController listener, + MapController listener, MapZoomPanBehavior zoomPanBehavior, - }) : defaultController = listener, + }) : controller = listener, _zoomPanBehavior = zoomPanBehavior { - _zoomPanBehavior.renderBox = this; + _zoomPanBehavior._renderBox = this; + _zoomPanBehavior._controller = controller; } - _DefaultController defaultController; + MapController controller; MapZoomPanBehavior get zoomPanBehavior => _zoomPanBehavior; MapZoomPanBehavior _zoomPanBehavior; @@ -488,7 +553,8 @@ class _RenderBehaviorView extends RenderBox { return; } _zoomPanBehavior = value; - value.renderBox = this; + value._renderBox = this; + value._controller = controller; } void _handleBehaviorChange() { @@ -498,21 +564,24 @@ class _RenderBehaviorView extends RenderBox { @override void attach(PipelineOwner owner) { super.attach(owner); - defaultController.addListener(_handleBehaviorChange); + controller.addListener(_handleBehaviorChange); } @override void detach() { - defaultController.removeListener(_handleBehaviorChange); + controller.removeListener(_handleBehaviorChange); super.detach(); } + @override + bool get isRepaintBoundary => true; + @override bool hitTestSelf(Offset position) => false; @override void performLayout() { - size = _getBoxSize(constraints); + size = getBoxSize(constraints); } @override diff --git a/packages/syncfusion_flutter_maps/lib/src/common/maps_shapes.dart b/packages/syncfusion_flutter_maps/lib/src/common/maps_shapes.dart deleted file mode 100644 index 72ba13896..000000000 --- a/packages/syncfusion_flutter_maps/lib/src/common/maps_shapes.dart +++ /dev/null @@ -1,301 +0,0 @@ -part of maps; - -/// Base class for map icon shapes. -class _MapIconShape { - const _MapIconShape(); - - /// Returns the size based on the value passed to it. - Size getPreferredSize(Size iconSize, SfMapsThemeData themeData) { - return iconSize; - } - - /// Paints the shapes based on the value passed to it. - void paint( - PaintingContext context, - Offset offset, { - RenderBox parentBox, - SfMapsThemeData themeData, - Size iconSize, - Color color, - Color strokeColor, - double strokeWidth, - MapIconType iconType, - }) { - iconSize = getPreferredSize(iconSize, themeData); - final double halfIconWidth = iconSize.width / 2; - final double halfIconHeight = iconSize.height / 2; - final bool hasStroke = strokeWidth != null && - strokeWidth > 0 && - strokeColor != null && - strokeColor != Colors.transparent; - final Paint paint = Paint() - ..isAntiAlias = true - ..color = color; - Path path; - - switch (iconType) { - case MapIconType.circle: - final Rect rect = Rect.fromLTWH( - offset.dx, offset.dy, iconSize.width, iconSize.height); - context.canvas.drawOval(rect, paint); - if (hasStroke) { - paint - ..strokeWidth = strokeWidth - ..color = strokeColor - ..style = PaintingStyle.stroke; - context.canvas.drawOval(rect, paint); - } - break; - case MapIconType.square: - final Rect rect = Rect.fromLTWH( - offset.dx, offset.dy, iconSize.width, iconSize.height); - context.canvas.drawRect(rect, paint); - if (hasStroke) { - paint - ..strokeWidth = strokeWidth - ..color = strokeColor - ..style = PaintingStyle.stroke; - context.canvas.drawRect(rect, paint); - } - break; - case MapIconType.triangle: - path = Path() - ..moveTo(offset.dx + halfIconWidth, offset.dy) - ..lineTo(offset.dx + iconSize.width, offset.dy + iconSize.height) - ..lineTo(offset.dx, offset.dy + iconSize.height) - ..close(); - context.canvas.drawPath(path, paint); - if (hasStroke) { - paint - ..strokeWidth = strokeWidth - ..color = strokeColor - ..style = PaintingStyle.stroke; - context.canvas.drawPath(path, paint); - } - break; - case MapIconType.diamond: - path = Path() - ..moveTo(offset.dx + halfIconWidth, offset.dy) - ..lineTo(offset.dx + iconSize.width, offset.dy + halfIconHeight) - ..lineTo(offset.dx + halfIconWidth, offset.dy + iconSize.height) - ..lineTo(offset.dx, offset.dy + halfIconHeight) - ..close(); - context.canvas.drawPath(path, paint); - if (hasStroke) { - paint - ..strokeWidth = strokeWidth - ..color = strokeColor - ..style = PaintingStyle.stroke; - context.canvas.drawPath(path, paint); - } - break; - } - } -} - -/// Base class for map tooltip shapes. -class _TooltipShape { - const _TooltipShape(); - - static const bool showTooltipNose = true; - static const double marginSpace = 6.0; - - /// Paints the tooltip shapes based on the value passed to it. - void paint( - PaintingContext context, - Offset offset, - Offset center, - TextPainter labelPainter, - Paint paint, - RenderProxyBox parentBox, - Animation tooltipAnimation, - SfMapsThemeData themeData, - MapTooltipSettings tooltipSettings) { - const double tooltipTriangleHeight = 7; - const double tooltipTriangleWidth = 12.0; - const double textPadding = 14.0; - const double halfTooltipTriangleWidth = tooltipTriangleWidth / 2; - const double elevation = 0.0; - - Path path = Path(); - double tooltipWidth; - double tooltipHeight; - if (parentBox.child != null) { - tooltipWidth = parentBox.child.size.width; - tooltipHeight = parentBox.child.size.height; - } else { - tooltipWidth = labelPainter.width + textPadding; - tooltipHeight = labelPainter.height + textPadding; - } - - final double halfTooltipWidth = tooltipWidth / 2; - final double halfTooltipHeight = tooltipHeight / 2; - - final double tooltipStartPoint = tooltipTriangleHeight + tooltipHeight / 2; - final double tooltipTriangleOffsetY = - tooltipStartPoint - tooltipTriangleHeight; - - final double endGlobal = parentBox.size.width - marginSpace; - double rightLineWidth = center.dx + halfTooltipWidth > endGlobal - ? endGlobal - center.dx - : halfTooltipWidth; - final double leftLineWidth = center.dx - halfTooltipWidth < marginSpace - ? center.dx - marginSpace - : tooltipWidth - rightLineWidth; - rightLineWidth = leftLineWidth < halfTooltipWidth - ? halfTooltipWidth - leftLineWidth + rightLineWidth - : rightLineWidth; - - double moveNosePoint = leftLineWidth < tooltipWidth * 0.2 - ? tooltipWidth * 0.2 - leftLineWidth - : 0.0; - moveNosePoint = rightLineWidth < tooltipWidth * 0.2 - ? -(tooltipWidth * 0.2 - rightLineWidth) - : moveNosePoint; - - double shiftText = leftLineWidth > rightLineWidth - ? -(halfTooltipWidth - rightLineWidth) - : 0.0; - shiftText = leftLineWidth < rightLineWidth - ? (halfTooltipWidth - leftLineWidth) - : shiftText; - - rightLineWidth = rightLineWidth + elevation; - path = _getTooltipPath( - path, - tooltipTriangleHeight, - halfTooltipHeight, - halfTooltipTriangleWidth, - tooltipTriangleOffsetY, - moveNosePoint, - rightLineWidth, - leftLineWidth, - themeData, - tooltipHeight); - - context.canvas.save(); - context.canvas.translate( - center.dx, center.dy - tooltipTriangleHeight - halfTooltipHeight); - context.canvas.scale(tooltipAnimation.value); - - if (parentBox.child == null || parentBox.child != null && showTooltipNose) { - context.canvas.drawPath(path, paint); - final Color strokeColor = - tooltipSettings.strokeColor ?? themeData.tooltipStrokeColor; - if (strokeColor != null && strokeColor != Colors.transparent) { - paint - ..color = strokeColor - ..strokeWidth = - tooltipSettings.strokeWidth ?? themeData.tooltipStrokeWidth - ..style = PaintingStyle.stroke; - context.canvas.drawPath(path, paint); - } - } - - if (parentBox.child == null) { - final double labelOffsetY = - tooltipTriangleHeight + halfTooltipHeight + labelPainter.height / 2; - labelPainter.paint( - context.canvas, - Offset(-labelPainter.width / 2 + shiftText, - tooltipStartPoint - labelOffsetY)); - } else { - context.canvas.clipPath(path); - context.paintChild(parentBox.child, - offset - _getShiftPosition(offset, center, parentBox)); - } - - context.canvas.restore(); - } - - Path _getTooltipPath( - Path path, - double tooltipTriangleHeight, - double halfTooltipHeight, - double halfTooltipTriangleWidth, - double tooltipTriangleOffsetY, - double moveNosePoint, - double rightLineWidth, - double leftLineWidth, - SfMapsThemeData themeData, - double tooltipHeight) { - final BorderRadius borderRadius = themeData.tooltipBorderRadius; - path.reset(); - - if (showTooltipNose) { - path.moveTo(0, tooltipTriangleHeight + halfTooltipHeight); - } else { - path.moveTo( - halfTooltipTriangleWidth + moveNosePoint, tooltipTriangleOffsetY); - } - - // / - path.lineTo( - halfTooltipTriangleWidth + moveNosePoint, tooltipTriangleOffsetY); - // ___ - // / - path.lineTo( - rightLineWidth - borderRadius.bottomRight.x, tooltipTriangleOffsetY); - // ___| - // / - path.quadraticBezierTo(rightLineWidth, tooltipTriangleOffsetY, - rightLineWidth, tooltipTriangleOffsetY - borderRadius.bottomRight.y); - path.lineTo(rightLineWidth, - tooltipTriangleOffsetY - tooltipHeight + borderRadius.topRight.y); - // _______ - // ___| - // / - path.quadraticBezierTo( - rightLineWidth, - tooltipTriangleOffsetY - tooltipHeight, - rightLineWidth - borderRadius.topRight.x, - tooltipTriangleOffsetY - tooltipHeight); - path.lineTo(-leftLineWidth + borderRadius.topLeft.x, - tooltipTriangleOffsetY - tooltipHeight); - // _______ - // | ___| - // / - path.quadraticBezierTo( - -leftLineWidth, - tooltipTriangleOffsetY - tooltipHeight, - -leftLineWidth, - tooltipTriangleOffsetY - tooltipHeight + borderRadius.topLeft.y); - path.lineTo( - -leftLineWidth, tooltipTriangleOffsetY - borderRadius.bottomLeft.y); - // ________ - // |___ ___| - // / - path.quadraticBezierTo(-leftLineWidth, tooltipTriangleOffsetY, - -leftLineWidth + borderRadius.bottomLeft.x, tooltipTriangleOffsetY); - path.lineTo( - -halfTooltipTriangleWidth + moveNosePoint, tooltipTriangleOffsetY); - // ________ - // |___ ___| - // \/ - path.close(); - - return path; - } - - Offset _getShiftPosition( - Offset offset, Offset center, RenderProxyBox parent) { - final Size childSize = parent.child.size; - final double halfChildWidth = childSize.width / 2; - final double halfChildHeight = childSize.height / 2; - - // Shifting the position of the tooltip to the left side, if its right - // edge goes out of the map's right edge. - if (center.dx + halfChildWidth + marginSpace > parent.size.width) { - return Offset( - 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. - else if (center.dx - halfChildWidth - marginSpace < offset.dx) { - return Offset(center.dx - marginSpace, halfChildHeight); - } - - return Offset(halfChildWidth, halfChildHeight); - } -} diff --git a/packages/syncfusion_flutter_maps/lib/src/common/settings.dart b/packages/syncfusion_flutter_maps/lib/src/common/settings.dart deleted file mode 100644 index 41912343f..000000000 --- a/packages/syncfusion_flutter_maps/lib/src/common/settings.dart +++ /dev/null @@ -1,2737 +0,0 @@ -part of maps; - -/// Signature used by the [MapShapeLayer.loadingBuilder]. -typedef MapLoadingBuilder = Widget Function(BuildContext context); - -/// Signature to return the string values from the data source -/// based on the index. -typedef IndexedStringValueMapper = String Function(int index); - -/// Signature to return the double values from the data source -/// based on the index. -typedef IndexedDoubleValueMapper = double Function(int index); - -/// Signature to return the colors or other types from the data source based on -/// the index based on which colors will be applied. -typedef IndexedColorValueMapper = dynamic Function(int index); - -/// Signature to return the [MapMarker]. -typedef MapMarkerBuilder = MapMarker Function(BuildContext context, int index); - -/// Signature for a [MapLayer.onWillZoom] callback which returns true or false -/// based on which current zooming completes. -typedef WillZoomCallback = bool Function(MapZoomDetails); - -/// Signature for a [MapLayer.onWillPan] callback which returns true or false -/// based on which current panning completes. -typedef WillPanCallback = bool Function(MapPanDetails); - -/// The delegate that maps the data source with the shape file and provides -/// data for the elements of the [SfMaps] like data labels, bubbles, tooltip, -/// and shape colors. -/// -/// The path of the .json file which contains the -/// GeoJSON data has to be set to the [MapShapeLayerDelegate.shapeFile]. -/// -/// The [MapShapeLayerDelegate.shapeDataField] property is used to -/// refer the unique field name in the .json file to identify each shapes and -/// map with the respective data in the data source. -/// -/// By default, the value specified for the -/// [MapShapeLayerDelegate.shapeDataField] in the GeoJSON file will be used in -/// the elements like data labels, tooltip, and legend for their respective -/// shapes. -/// -/// However, it is possible to keep a data source and customize these elements -/// based on the requirement. The value of the -/// [MapShapeLayerDelegate.shapeDataField] will be used to map with the -/// respective data returned in [MapShapeLayerDelegate.primaryValueMapper] -/// from the data source. -/// -/// Once the above mapping is done, you can customize the elements using the -/// APIs like [MapShapeLayerDelegate.dataLabelMapper], -/// [MapShapeLayerDelegate.shapeColorMappers], -/// [MapShapeLayerDelegate.shapeTooltipTextMapper], etc. -/// -/// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return -/// SfMaps( -/// layers: [ -/// MapShapeLayer( -/// delegate: MapShapeLayerDelegate( -/// shapeFile: "assets/world_map.json", -/// shapeDataField: "name", -/// dataCount: data.length, -/// primaryValueMapper: (index) { -/// return data[index].country; -/// }, -/// dataLabelMapper: (index) { -/// return data[index].countryCode; -/// }), -/// ) -/// ], -/// ); -/// } -/// ``` -/// See also: -/// * [MapShapeLayerDelegate.primaryValueMapper], to map the data of the data -/// source collection with the respective -/// [MapShapeLayerDelegate.shapeDataField] in .json file. -/// * [MapShapeLayerDelegate.bubbleSizeMapper], to customize the bubble size. -/// * [MapShapeLayerDelegate.dataLabelMapper], to customize the -/// data label's text. -/// * [MapShapeLayerDelegate.shapeTooltipTextMapper], to customize the -/// shape tooltip text. -/// * [MapShapeLayerDelegate.bubbleTooltipTextMapper], to customize the -/// bubble tooltip text. -/// * [MapShapeLayerDelegate.shapeColorValueMapper] and -/// [MapShapeLayerDelegate.shapeColorMappers], to customize the shape colors. -/// * [MapShapeLayerDelegate.bubbleColorValueMapper] and -/// [MapShapeLayerDelegate.bubbleColorMappers], to customize the -/// bubble colors. -class MapShapeLayerDelegate { - /// Creates a [MapShapeLayerDelegate]. - const MapShapeLayerDelegate({ - @required this.shapeFile, - this.shapeDataField, - this.dataCount, - this.primaryValueMapper, - this.shapeColorMappers, - this.bubbleColorMappers, - this.dataLabelMapper, - this.shapeTooltipTextMapper, - this.bubbleTooltipTextMapper, - this.bubbleSizeMapper, - this.shapeColorValueMapper, - this.bubbleColorValueMapper, - }) : assert(shapeFile != null), - assert(dataCount == null || dataCount > 0), - assert(primaryValueMapper == null || - (primaryValueMapper != null && dataCount != null && dataCount > 0)), - assert(shapeColorMappers == null || - (shapeColorMappers != null && - primaryValueMapper != null && - shapeColorValueMapper != null)), - assert(bubbleColorMappers == null || - (bubbleColorMappers != null && - primaryValueMapper != null && - bubbleColorValueMapper != null)); - - /// Path of the GeoJSON data file. - final String shapeFile; - - /// Field name in the .json file to identify each shape. - /// - /// It is used to refer the field name in the .json file to identify - /// each shape and map that shape with the respective data in - /// the data source. - final String shapeDataField; - - /// Length of the data source. - final int dataCount; - - /// Collection of [MapColorMapper] which specifies shape's color based on the - /// data. - /// - /// It provides option to set the shape color based on the specific - /// [MapColorMapper.value] or the range of values which falls between - /// [MapColorMapper.from] and [MapColorMapper.to]. - /// - /// Based on the returned values, legend items will be rendered. The text of - /// legend item will be [MapColorMapper.text] of the [MapColorMapper]. - /// - /// The below code snippet represents how color can be applied to the shape - /// based on the [MapColorMapper.value] property of [MapColorMapper]. - /// - /// ```dart - /// List data; - /// - /// @override - /// void initState() { - /// super.initState(); - /// - /// data = [ - /// Model('India', 280, "Low"), - /// Model('United States of America', 190, "High"), - /// Model('Pakistan', 37, "Low"), - /// ]; - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].storage; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(value: "Low", color: Colors.red), - /// MapColorMapper(value: "High", color: Colors.green) - /// ]), - /// ) - /// ], - /// ); - /// } - /// ``` - /// The below code snippet represents how color can be applied to the shape - /// based on the range between [MapColorMapper.from] and [MapColorMapper.to] - /// properties of [MapColorMapper]. - /// - /// ```dart - /// List data; - /// - /// @override - /// void initState() { - /// super.initState(); - /// - /// data = [ - /// Model('India', 100, "Low"), - /// Model('United States of America', 200, "High"), - /// Model('Pakistan', 75, "Low"), - /// ]; - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].count; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.red), - /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) - /// ]), - /// ) - /// ], - /// ); - /// } - /// ``` - final List shapeColorMappers; - - /// Collection of [MapColorMapper] which specifies bubble's color - /// based on the data. - /// - /// It provides option to set the bubble color based on the specific - /// [MapColorMapper.value] or the range of values which falls between - /// [MapColorMapper.from] and [MapColorMapper.to]. - /// - /// The below code snippet represents how color can be applied to the bubble - /// based on the [MapColorMapper.value] property of [MapColorMapper]. - /// - /// ```dart - /// List data; - /// - /// @override - /// void initState() { - /// super.initState(); - /// - /// data = [ - /// Model('India', 280, "Low"), - /// Model('United States of America', 190, "High"), - /// Model('Pakistan', 37, "Low"), - /// ]; - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// showBubbles: true, - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// bubbleColorValueMapper: (index) { - /// return data[index].usersCount; - /// }, - /// bubbleSizeMapper: (index) { - /// return data[index].usersCount; - /// }, - /// bubbleColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.red), - /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) - /// ]), - /// ) - /// ], - /// ); - /// } - /// ``` - /// The below code snippet represents how color can be applied to the bubble - /// based on the range between [MapColorMapper.from] and [MapColorMapper.to] - /// properties of [MapColorMapper]. - /// - /// ```dart - /// List data; - /// - /// @override - /// void initState() { - /// super.initState(); - /// - /// data = [ - /// Model('India', 280, "Low"), - /// Model('United States of America', 190, "High"), - /// Model('Pakistan', 37, "Low"), - /// ]; - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// showBubbles: true, - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// bubbleColorValueMapper: (index) { - /// return data[index].storage; - /// }, - /// bubbleSizeMapper: (index) { - /// return data[index].usersCount; - /// }, - /// bubbleColorMappers: [ - /// MapColorMapper(value: "Low", color: Colors.red), - /// MapColorMapper(value: "High", color: Colors.yellow) - /// ]), - /// ) - /// ], - /// ); - /// } - /// ``` - final List bubbleColorMappers; - - /// Returns the the primary value for the every data in the data source - /// collection. - /// - /// This primary value will be mapped with the [shapeDataField] value in the - /// respective shape detail in the .json file. This mapping will then be used - /// in the rendering of bubbles, data labels, shape colors, tooltip - /// in their respective shape's coordinates. - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// } - /// ), - /// ) - /// ], - /// ); - /// } - /// ``` - final IndexedStringValueMapper primaryValueMapper; - - /// Returns the data label text for each shape. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showDataLabels: true, - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// dataLabelMapper: (index) { - /// return bubbleData[index].country; - /// } - /// ), - /// ) - /// ], - /// ); - /// } - /// ``` - final IndexedStringValueMapper dataLabelMapper; - - /// Returns the tooltip text for each shape. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// enableShapeTooltip: true, - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// shapeTooltipTextMapper: (index) { - /// return bubbleData[index].country; - /// } - /// ), - /// ) - /// ], - /// ); - /// } - /// ``` - final IndexedStringValueMapper shapeTooltipTextMapper; - - /// Returns the tooltip text for the bubble in the shape. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showBubbles: true, - /// enableBubbleTooltip: true, - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleTooltipTextMapper: (index) { - /// return bubbleData[index].country; - /// } - /// ), - /// ) - /// ], - /// ); - /// } - /// ``` - final IndexedStringValueMapper bubbleTooltipTextMapper; - - /// Returns a value based on which bubble size will be calculated. - /// - /// The minimum and maximum size of the bubble can be customized using the - /// [MapBubbleSettings.minRadius] and [MapBubbleSettings.maxRadius]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showBubbles: true, - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// } - /// ), - /// ) - /// ], - /// ); - /// } - /// ``` - final IndexedDoubleValueMapper bubbleSizeMapper; - - /// Returns a color or value based on which shape color will be updated. - /// - /// If this returns a color, then this color will be applied to the shape - /// straightaway. - /// - /// If it returns a value other than the color, then you must set the - /// [MapShapeLayerDelegate.shapeColorMappers] property. - /// - /// The value returned from the [shapeColorValueMapper] will be used for the - /// comparison in the [MapColorMapper.value] or [MapColorMapper.from] and - /// [MapColorMapper.to]. Then, the [MapColorMapper.color] will be applied to - /// the respective shape. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return bubbleData[index].country; - /// } - /// ), - /// ) - /// ], - /// ); - /// } - /// ``` - final IndexedColorValueMapper shapeColorValueMapper; - - /// Returns a color or value based on which bubble color will be updated. - /// - /// If this returns a color, then this color will be applied to the bubble - /// straightaway. - /// - /// If it returns a value other than the color, then you must set the - /// [MapShapeLayerDelegate.bubbleColorMappers] property. - /// - /// The value returned from the [bubbleColorValueMapper] will be used for the - /// comparison in the [MapColorMapper.value] or [MapColorMapper.from] and - /// [MapColorMapper.to]. Then, the [MapColorMapper.color] will be applied to - /// the respective bubble. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showBubbles: true, - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleColorValueMapper: (index) { - /// return bubbleData[index].country; - /// } - /// ), - /// ) - /// ], - /// ); - /// } - /// ``` - final IndexedColorValueMapper bubbleColorValueMapper; -} - -/// Title for the [SfMaps]. -/// -/// [MapTitle] can define and customize the title for the [SfMaps]. The text -/// property of the [MapTitle] is used to set the text of the title. -// -/// It also provides option for customizing text style, alignment, decoration, -/// background color, margin and padding. -/// -/// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return SfMaps( -/// title: MapTitle( -/// text : 'World map' -/// ), -/// ); -/// } -/// ``` -class MapTitle { - /// Creates a [MapTitle]. - const MapTitle({ - this.text, - this.textStyle, - this.alignment, - this.decoration, - this.color, - this.margin, - this.padding, - }); - - /// Specifies the text to be displayed as map title. - /// - /// See also: - /// * [textStyle], to customize the text. - /// * [alignment], to align the title. - final String text; - - /// Customizes the style of the [text]. - /// - /// See also: - /// * [text], to set the text for the title. - final TextStyle textStyle; - - /// Specifies the position of the title. - /// - /// Defaults to `center`. - /// - /// See also: - /// * [text], to set the text for the title. - final AlignmentGeometry alignment; - - /// Customize the appearance of the title. - /// - /// See also: - /// * [Decoration], to set the decoration for the title. - /// * [text], to set the text for the title. - final Decoration decoration; - - /// Specifies the background color of the title. - /// - /// See also: - /// * [text], to set the text for the title. - final Color color; - - /// Customizes the margin of the title. - /// - /// See also: - /// * [EdgeInsetsGeometry], to use the EdgeInsets values. - /// * [text], to set the text for the title. - final EdgeInsetsGeometry margin; - - /// Customize the space around the title [text]. - /// - /// See also: - /// * [EdgeInsetsGeometry], to use the EdgeInsets values. - /// * [text], to set the text for the title. - final EdgeInsetsGeometry padding; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MapTitle && - other.text == text && - other.textStyle == textStyle && - other.alignment == alignment && - other.decoration == decoration && - other.color == color && - other.margin == margin && - other.padding == padding; - } - - @override - int get hashCode => hashValues( - text, textStyle, alignment, decoration, color, margin, padding); -} - -/// Customizes the shape or bubble color based on the data source and sets the -/// text and icon color for legend items. -/// -/// [MapShapeLayerDelegate.shapeColorMappers] and -/// [MapShapeLayerDelegate.bubbleColorMappers] accepts collection of -/// [MapColorMapper]. -/// -/// [MapShapeLayerDelegate.shapeColorValueMapper] and -/// [MapShapeLayerDelegate.bubbleColorValueMapper] returns a color or value -/// based on which shape or bubble color will be updated. -/// -/// If they return a color, then this color will be applied to the shapes or -/// bubbles straightaway. -/// -/// If they return a value other than the color, then you must set the -/// [MapShapeLayerDelegate.shapeColorMappers] or -/// [MapShapeLayerDelegate.bubbleColorMappers] property. -/// -/// The value returned from the [MapShapeLayerDelegate.shapeColorValueMapper] -/// and [MapShapeLayerDelegate.bubbleColorValueMapper] will be used for the -/// comparison in the [MapColorMapper.value] or [MapColorMapper.from] and -/// [MapColorMapper.to]. Then, the [MapColorMapper.color] will be applied to -/// the respective shape or bubble. -/// -/// Legend icon's color and text will be taken from [MapColorMapper.color] or -/// [MapColorMapper.text] respectively. -/// -/// The below code snippet represents how color can be applied to the shape -/// based on the [MapColorMapper.value] property of [MapColorMapper]. -/// -/// ```dart -/// List data; -/// -/// @override -/// void initState() { -/// super.initState(); -/// -/// data = [ -/// Model('India', 280, "Low"), -/// Model('United States of America', 190, "High"), -/// Model('Pakistan', 37, "Low"), -/// ]; -/// } -/// -/// @override -/// Widget build(BuildContext context) { -/// return SfMaps( -/// layers: [ -/// MapShapeLayer( -/// delegate: MapShapeLayerDelegate( -/// shapeFile: "assets/world_map.json", -/// shapeDataField: "name", -/// dataCount: data.length, -/// primaryValueMapper: (index) { -/// return data[index].country; -/// }, -/// shapeColorValueMapper: (index) { -/// return data[index].storage; -/// }, -/// shapeColorMappers: [ -/// MapColorMapper(value: "Low", color: Colors.red), -/// MapColorMapper(value: "High", color: Colors.green) -/// ]), -/// ) -/// ], -/// ); -/// } -/// ``` -/// The below code snippet represents how color can be applied to the shape -/// based on the range between [MapColorMapper.from] and [MapColorMapper.to] -/// properties of [MapColorMapper]. -/// -/// ```dart -/// List data; -/// -/// @override -/// void initState() { -/// super.initState(); -/// -/// data = [ -/// Model('India', 100, "Low"), -/// Model('United States of America', 200, "High"), -/// Model('Pakistan', 75, "Low"), -/// ]; -/// } -/// -/// @override -/// Widget build(BuildContext context) { -/// return SfMaps( -/// layers: [ -/// MapShapeLayer( -/// delegate: MapShapeLayerDelegate( -/// shapeFile: "assets/world_map.json", -/// shapeDataField: "name", -/// dataCount: data.length, -/// primaryValueMapper: (index) { -/// return data[index].country; -/// }, -/// shapeColorValueMapper: (index) { -/// return data[index].count; -/// }, -/// shapeColorMappers: [ -/// MapColorMapper(from: 0, to: 100, color: Colors.red), -/// MapColorMapper(from: 101, to: 200, color: Colors.yellow) -/// ]), -/// ) -/// ], -/// ); -/// } -/// ``` -/// -/// See also: -/// * [MapShapeLayerDelegate.shapeColorValueMapper] and -/// [MapShapeLayerDelegate.shapeColorMappers], to customize the shape colors -/// based on the data. -/// * [MapShapeLayerDelegate.bubbleColorValueMapper] and -/// [MapShapeLayerDelegate.bubbleColorMappers], to customize the shape colors -/// based on the data. -class MapColorMapper { - /// Creates a [MapColorMapper]. - const MapColorMapper({ - this.from, - this.to, - this.value, - this.color, - 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); - - /// Sets the range start for the color mapping. - /// - /// The shape or bubble will render in the specified [color] if the value - /// returned in the [MapShapeLayerDelegate.shapeColorValueMapper] or - /// [MapShapeLayerDelegate.bubbleColorValueMapper] falls between the [from] - /// and [to] range. - /// - /// ```dart - /// List data; - /// - /// @override - /// void initState() { - /// super.initState(); - /// - /// data = [ - /// Model('India', 100, "Low"), - /// Model('United States of America', 200, "High"), - /// Model('Pakistan', 75, "Low"), - /// ]; - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].count; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.red), - /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) - /// ]), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [to], to set the range end for the range color mapping. - /// * [color], to set the color for the shape. - /// * [MapShapeLayerDelegate.shapeColorMappers], to set the shape colors - /// based on the specific - /// value. - /// * [MapShapeLayerDelegate.bubbleColorMappers], to set the bubble colors - /// based on the specific - /// value. - final double from; - - /// Sets the range end for the color mapping. - /// - /// The shape or bubble will render in the specified [color] if the value - /// returned in the [MapShapeLayerDelegate.shapeColorValueMapper] or - /// [MapShapeLayerDelegate.bubbleColorValueMapper] falls between the [from] - /// and [to] range. - /// - /// ```dart - /// List data; - /// - /// @override - /// void initState() { - /// super.initState(); - /// - /// data = [ - /// Model('India', 100, "Low"), - /// Model('United States of America', 200, "High"), - /// Model('Pakistan', 75, "Low"), - /// ]; - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].count; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.red), - /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) - /// ]), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [from], to set the range end for the range color mapping. - /// * [color], to set the color for the shape. - /// * [MapShapeLayerDelegate.shapeColorMappers], to set the shape colors based - /// on the specific value. - /// * [MapShapeLayerDelegate.bubbleColorMappers], to set the bubble colors - /// based on the specific value. - final double to; - - /// Sets the value for the color mapping. - /// - /// The shape or bubble will render in the specified [color] if the value - /// returned in the [MapShapeLayerDelegate.shapeColorValueMapper] or - /// [MapShapeLayerDelegate.bubbleColorValueMapper] is equal to this [value]. - /// - /// ```dart - /// List data; - /// - /// @override - /// void initState() { - /// super.initState(); - /// - /// data = [ - /// Model('India', 280, "Low"), - /// Model('United States of America', 190, "High"), - /// Model('Pakistan', 37, "Low"), - /// ]; - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].storage; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(value: "Low", color: Colors.red), - /// MapColorMapper(value: "High", color: Colors.green) - /// ]), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [color], to set the color for the shape. - /// * [MapShapeLayerDelegate.shapeColorMappers], to set the shape colors - /// based on the specific value. - /// * [MapShapeLayerDelegate.bubbleColorMappers], to set the bubble colors - /// based on the specific value. - final String value; - - /// Specifies the color applies to the shape or bubble based on the value. - /// - /// ```dart - /// List data; - /// - /// @override - /// void initState() { - /// super.initState(); - /// - /// data = [ - /// Model('India', 280, "Low"), - /// Model('United States of America', 190, "High"), - /// Model('Pakistan', 37, "Low"), - /// ]; - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].storage; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(value: "Low", color: Colors.red), - /// MapColorMapper(value: "High", color: Colors.green) - /// ]), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [from], to set the range end for the range color mapping. - /// * [to], to set the range end for the range color mapping. - /// * [value], to set the value for the equal color mapping. - /// * [MapShapeLayerDelegate.shapeColorMappers], to set the shape colors - /// based on the specific value. - /// * [MapShapeLayerDelegate.bubbleColorMappers], to set the bubble colors - /// based on the specific value. - final Color color; - - /// Specifies the minimum opacity applies to the shape or bubble while using - /// [from] and [to]. - /// - /// The shapes or bubbles with lowest value which is [from] will be applied a - /// [minOpacity] and the shapes or bubbles with highest value which is [to] - /// will be applied a [maxOpacity]. The shapes or bubbles with values in- - /// between the range will get a opacity based on their respective value. - /// - /// ```dart - /// List data; - /// - /// @override - /// void initState() { - /// super.initState(); - /// - /// data = [ - /// Model('India', 100, "Low"), - /// Model('United States of America', 200, "High"), - /// Model('Pakistan', 75, "Low"), - /// ]; - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].count; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.yellow, - /// maxOpacity: 0.2, minOpacity: 0.5), - /// MapColorMapper(from: 101, to: 200, color: Colors.red, - /// maxOpacity: 0.6, minOpacity: 1) - /// ]), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [MapShapeLayerDelegate.shapeColorMappers], to set the shape colors - /// based on the specific value. - /// * [MapShapeLayerDelegate.bubbleColorMappers], to set the bubble colors - /// based on the specific value. - final double minOpacity; - - /// Specifies the maximum opacity applies to the shape or bubble while using - /// [from] and [to]. - /// - /// The shapes or bubbles with lowest value which is [from] will be applied a - /// [minOpacity] and the shapes or bubbles with highest value which is [to] - /// will be applied a [maxOpacity]. The shapes or bubbles with values in- - /// between the range will get a opacity based on their respective value. - /// - /// ```dart - /// List data; - /// - /// @override - /// void initState() { - /// super.initState(); - /// - /// data = [ - /// Model('India', 100, "Low"), - /// Model('United States of America', 200, "High"), - /// Model('Pakistan', 75, "Low"), - /// ]; - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].count; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.yellow, - /// maxOpacity: 0.2, minOpacity: 0.5), - /// MapColorMapper(from: 101, to: 200, color: Colors.red, - /// maxOpacity: 0.6, minOpacity: 1) - /// ]), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [MapShapeLayerDelegate.shapeColorMappers], to set the shape colors based - /// on the specific value. - /// * [MapShapeLayerDelegate.bubbleColorMappers], to set the bubble colors - /// based on the specific value. - final double maxOpacity; - - /// Specifies the text to be used for the legend item. - /// - /// By default, [MapColorMapper.from] and [MapColorMapper.to] or - /// [MapColorMapper.value] will be used as the text of the legend item. - /// - /// ```dart - /// List data; - /// - /// @override - /// void initState() { - /// super.initState(); - /// - /// data = [ - /// Model('India', 100, "Low"), - /// Model('United States of America', 200, "High"), - /// Model('Pakistan', 75, "Low"), - /// ]; - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].count; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.yellow, - /// maxOpacity: 0.2, minOpacity: 0.5, text: "low"), - /// MapColorMapper(from: 101, to: 200, color: Colors.red, - /// maxOpacity: 0.6, minOpacity: 1, text: "high") - /// ]), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [MapShapeLayerDelegate.shapeColorMappers], to set the shape colors based - /// on the specific value. - final String text; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MapColorMapper && - other.from == from && - other.to == to && - other.value == value && - other.color == color && - other.minOpacity == minOpacity && - other.maxOpacity == maxOpacity && - other.text == text; - } - - @override - int get hashCode => - hashValues(from, to, value, color, minOpacity, maxOpacity, text); -} - -/// Customizes the legend's and legend item's position, appearance and toggling -/// behavior. -/// -/// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return SfMaps( -/// layers: [ -/// MapShapeLayer( -/// showLegend: true, -/// legendSettings: MapLegendSettings( -/// padding: EdgeInsets.all(10) -/// ), -/// delegate: MapShapeLayerDelegate( -/// shapeFile: "assets/world_map.json", -/// shapeDataField: "continent", -/// dataCount: bubbleData.length, -/// primaryValueMapper: (index) { -/// return bubbleData[index].country; -/// }), -/// ) -/// ], -/// ); -/// } -/// ``` -@immutable -class MapLegendSettings { - /// Creates a [MapLegendSettings]. - const MapLegendSettings({ - this.direction, - this.padding = const EdgeInsets.all(10.0), - this.position = MapLegendPosition.top, - this.offset, - this.itemsSpacing = 10.0, - this.iconType = MapIconType.circle, - this.textStyle, - this.showIcon = true, - this.iconSize = const Size(8.0, 8.0), - this.overflowMode = MapLegendOverflowMode.wrap, - this.enableToggleInteraction = false, - this.toggledItemColor, - this.toggledItemStrokeColor, - this.toggledItemStrokeWidth = 1.0, - this.toggledItemOpacity = 1.0, - }) : assert(itemsSpacing != null && itemsSpacing >= 0), - assert(padding != null), - assert(iconSize != null), - assert(toggledItemStrokeWidth == null || toggledItemStrokeWidth >= 0), - assert(toggledItemOpacity == null || - (toggledItemOpacity >= 0 && toggledItemOpacity <= 1)); - - /// Sets the padding around the legend. - /// - /// Defaults to EdgeInsets.all(10.0). - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// padding: EdgeInsets.all(10) - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - final EdgeInsetsGeometry padding; - - /// Arranges the legend items in either horizontal or vertical direction. - /// - /// Defaults to horizontal, if the [position] is top, bottom or null. - /// Defaults to vertical, if the [position] is left or right. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// direction: Axis.horizontal - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [position], to set the position of the legend. - final Axis direction; - - /// Positions the legend in the different directions. - /// - /// Defaults to [MapLegendPosition.top]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// position: MapLegendPosition.bottom - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// See also: - /// * [offset], to place the legend in custom position. - final MapLegendPosition position; - - /// Places the legend in custom position. - /// - /// If the [offset] has been set and if the - /// [position] is top, then the legend will be placed in top but with - /// absolute position i.e. legend will not take dedicated position for it and - /// will be drawn on the top of map. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// offset: Offset(0, 5) - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [position], to set the position of the legend. - final Offset offset; - - /// Specifies the space between the each legend items. - /// - /// Defaults to 10.0. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// itemsSpacing: 10 - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - final double itemsSpacing; - - /// Specifies the shape of the legend icon. - /// - /// Defaults to [MapIconType.circle]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// iconType: MapIconType.square - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [showIcon], to show or hide the icon for the legend items. - /// * [iconSize], to set the size of the icon. - final MapIconType iconType; - - /// Customizes the legend item's text style. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// textStyle: TextStyle(color: Colors.red) - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - final TextStyle textStyle; - - /// Shows or hides the legend icons. - /// - /// Defaults to `true`. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// showIcon: true - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [iconType], to set the icon type for the legend items. - /// * [iconSize], to set size of the icons. - final bool showIcon; - - /// Wraps or scrolls the legend items when it overflows. - /// - /// In wrap mode, overflowed items will be wrapped in a new row and will - /// be positioned from the start. - /// - /// If the legend position is left or right, scroll direction is vertical. - /// If the legend position is top or bottom, scroll direction is horizontal. - /// - /// Defaults to [MapLegendOverflowMode.wrap]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// overflowMode: MapLegendOverflowMode.scroll - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [position], to set the position of the legend. - /// * [direction], to set the direction of the legend. - final MapLegendOverflowMode overflowMode; - - /// Customizes the size of the icon. - /// - /// Defaults to Size(12.0, 12.0). - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// iconSize: Size(30, 10) - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [showIcon], to show or hide the icon for the legend items. - /// * [iconType], to set the size of the icon. - final Size iconSize; - - /// Enables the toggle interaction for the legend. - /// - /// Defaults to false. - /// - /// When this is enabled, respective shape for the legend item will be - /// toggled on tap or click. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// enableToggleInteraction: true - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - final bool enableToggleInteraction; - - /// Fills the toggled legend item's icon and the respective shape or bubble - /// by this color. - /// - /// This snippet shows how to set toggledItemColor in [SfMaps]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// enableToggleInteraction: true, - /// toggledItemColor: Colors.blueGrey - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [toggledItemStrokeColor], [toggledItemStrokeWidth], to set the stroke - /// for the toggled legend item's shape or bubble. - final Color toggledItemColor; - - /// Stroke color for the toggled legend item's respective shape or bubble. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// enableToggleInteraction: true, - /// toggledItemColor: Colors.blueGrey, - /// toggledItemStrokeColor: Colors.white, - /// toggledItemStrokeWidth: 0.5 - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [toggledItemStrokeWidth], to set the stroke width for the - /// toggled legend item's shape. - final Color toggledItemStrokeColor; - - /// Stroke width for the toggled legend item's respective shape or - /// bubble. - /// - /// Defaults to 1.0. - /// - /// This snippet shows how to set toggledItemStrokeWidth in [SfMaps]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// enableToggleInteraction: true, - /// toggledItemColor: Colors.blueGrey, - /// toggledItemStrokeColor: Colors.white, - /// toggledItemStrokeWidth: 0.5 - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [toggledItemStrokeColor], to set the stroke color for the - /// toggled legend item's shape. - final double toggledItemStrokeWidth; - - /// Sets the toggled legend item's respective shape or - /// bubble opacity. - /// - /// Defaults to 1.0. - /// - /// This snippet shows how to set toggledItemOpacity in [SfMaps]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// showLegend: true, - /// legendSettings: MapLegendSettings( - /// enableToggleInteraction: true, - /// toggledItemColor: Colors.blueGrey, - /// toggledItemStrokeColor: Colors.white, - /// toggledItemStrokeWidth: 0.5, - /// toggledItemOpacity: 0.5 - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - final double toggledItemOpacity; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MapLegendSettings && - other.padding == padding && - other.offset == offset && - other.showIcon == showIcon && - other.enableToggleInteraction == enableToggleInteraction && - other.iconSize == iconSize && - other.iconType == iconType && - other.itemsSpacing == itemsSpacing && - other.direction == direction && - other.overflowMode == overflowMode && - other.position == position && - other.textStyle == textStyle && - other.toggledItemColor == toggledItemColor && - other.toggledItemStrokeColor == toggledItemStrokeColor && - other.toggledItemStrokeWidth == toggledItemStrokeWidth && - other.toggledItemOpacity == toggledItemOpacity; - } - - @override - int get hashCode => hashValues( - padding, - offset, - showIcon, - enableToggleInteraction, - iconSize, - iconType, - itemsSpacing, - direction, - overflowMode, - position, - textStyle, - toggledItemColor, - toggledItemStrokeColor, - toggledItemStrokeWidth, - toggledItemOpacity); - - /// Creates a copy of this class but with the given fields - /// replaced with the new values. - MapLegendSettings _copyWith({ - Axis direction, - EdgeInsetsGeometry padding, - MapLegendPosition position, - Offset offset, - double itemsSpacing, - MapIconType iconType, - TextStyle textStyle, - bool showIcon, - Size iconSize, - MapLegendOverflowMode overflowMode, - bool enableToggleInteraction, - Color toggledItemColor, - Color toggledItemStrokeColor, - double toggledItemStrokeWidth, - double toggledItemOpacity, - }) { - return MapLegendSettings( - direction: direction ?? this.direction, - padding: padding ?? this.padding, - position: position ?? this.position, - offset: offset ?? this.offset, - itemsSpacing: itemsSpacing ?? this.itemsSpacing, - iconType: iconType ?? this.iconType, - textStyle: textStyle ?? this.textStyle, - showIcon: showIcon ?? this.showIcon, - iconSize: iconSize ?? this.iconSize, - overflowMode: overflowMode ?? this.overflowMode, - enableToggleInteraction: - enableToggleInteraction ?? this.enableToggleInteraction, - toggledItemColor: toggledItemColor ?? this.toggledItemColor, - toggledItemStrokeColor: - toggledItemStrokeColor ?? this.toggledItemStrokeColor, - toggledItemStrokeWidth: - toggledItemStrokeWidth ?? this.toggledItemStrokeWidth, - toggledItemOpacity: toggledItemOpacity ?? this.toggledItemOpacity, - ); - } -} - -/// Customizes the appearance of the data labels. -/// -/// It is possible to customize the style of the data labels, hide or trim the -/// data labels when it exceeds their respective shapes. -/// -/// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return -/// SfMaps( -/// layers: [ -/// MapShapeLayer( -/// dataLabelSettings: -/// MapDataLabelSettings( -/// textStyle: TextStyle(color: Colors.red) -/// ), -/// delegate: MapShapeLayerDelegate( -/// showDataLabels: true, -/// shapeFile: "assets/world_map.json", -/// shapeDataField: "continent", -/// dataCount: bubbleData.length, -/// primaryValueMapper: (index) { -/// return bubbleData[index].country; -/// }), -/// ) -/// ], -/// ); -/// } -/// ``` -@immutable -class MapDataLabelSettings { - /// Creates a [MapDataLabelSettings]. - const MapDataLabelSettings({ - this.textStyle, - this.overflowMode = MapLabelOverflowMode.none, - }); - - /// Customizes the data label's text style. - /// - /// This snippet shows how to set [textStyle] for the data labels in [SfMaps]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// dataLabelSettings: - /// MapDataLabelSettings( - /// textStyle: TextStyle(color: Colors.red) - /// ), - /// delegate: MapShapeLayerDelegate( - /// showDataLabels: true, - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - final TextStyle textStyle; - - /// Trim or removes the data label when it is overflowed from the shape. - /// - /// Defaults to [MapLabelOverflowMode.none]. - /// - /// By default, the data labels will render even if it overflows form the - /// shape. Using this property, it is possible to remove or trim the data - /// labels based on the available space. - /// - /// This snippet shows how to set the [overflowMode] for the data labels in - /// [SfMaps]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// dataLabelSettings: - /// MapDataLabelSettings( - /// overflowMode: MapLabelOverflowMode.hide - /// ), - /// delegate: MapShapeLayerDelegate( - /// showDataLabels: true, - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - final MapLabelOverflowMode overflowMode; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MapDataLabelSettings && - other.textStyle == textStyle && - other.overflowMode == overflowMode; - } - - @override - int get hashCode => hashValues(textStyle, overflowMode); -} - -/// Customizes the appearance of the bubbles. -/// -/// It is possible to customize the radius, color, opacity and stroke of the -/// bubbles. -/// -/// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return -/// SfMaps( -/// layers: [ -/// MapShapeLayer( -/// bubbleSettings: MapBubbleSettings(maxRadius: 10, minRadius: 2), -/// delegate: MapShapeLayerDelegate( -/// shapeFile: "assets/world_map.json", -/// shapeDataField: "name", -/// showBubbles: true, -/// dataCount: bubbleData.length, -/// primaryValueMapper: (index) { -/// return bubbleData[index].country; -/// }, -/// bubbleSizeMapper: (index) { -/// return bubbleData[index].usersCount; -/// }), -/// ) -/// ], -/// ); -/// } -/// ``` -@immutable -class MapBubbleSettings { - /// Creates a [MapBubbleSettings]. - const MapBubbleSettings({ - this.minRadius = 10.0, - this.maxRadius = 50.0, - this.color, - this.opacity = 0.75, - this.strokeWidth, - this.strokeColor, - }); - - /// Minimum radius of the bubble. - /// - /// The radius of the bubble depends on the value returned in the - /// [MapShapeLayerDelegate.bubbleSizeMapper]. From all the returned values, - /// the lowest value will have [minRadius] and the highest value will have - /// [maxRadius]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// bubbleSettings: MapBubbleSettings(maxRadius: 10, minRadius: 2), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// showBubbles: true, - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - final double minRadius; - - /// Maximum radius of the bubble. - /// - /// The radius of the bubble depends on the value returned in the - /// [MapShapeLayerDelegate.bubbleSizeMapper]. From all the returned values, - /// the lowest value will have [minRadius] and the highest value will have - /// [maxRadius]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// bubbleSettings: MapBubbleSettings(maxRadius: 10, minRadius: 2), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// showBubbles: true, - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - final double maxRadius; - - /// Default color of the bubbles. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// bubbleSettings: MapBubbleSettings( - /// color: Colors.black), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// showBubbles: true, - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [MapShapeLayerDelegate.bubbleColorMappers] and - /// [MapShapeLayerDelegate.bubbleColorValueMapper], to customize the bubble - /// colors based on the data. - final Color color; - - /// Opacity of the bubbles. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// bubbleSettings: MapBubbleSettings( - /// opacity: 0.5), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// showBubbles: true, - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - final double opacity; - - /// Stroke width of the bubbles. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// bubbleSettings: MapBubbleSettings( - /// strokeColor: Colors.red, - /// strokeWidth: 2), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// showBubbles: true, - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [strokeColor], to set the stroke color. - final double strokeWidth; - - /// Stroke color of the bubbles. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// bubbleSettings: MapBubbleSettings( - /// strokeColor: Colors.red, - /// strokeWidth: 2), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// showBubbles: true, - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [strokeWidth], to set the stroke width. - final Color strokeColor; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MapBubbleSettings && - other.color == color && - other.strokeWidth == strokeWidth && - other.strokeColor == strokeColor && - other.minRadius == minRadius && - other.maxRadius == maxRadius && - other.opacity == opacity; - } - - @override - int get hashCode => hashValues( - color, strokeWidth, strokeColor, maxRadius, minRadius, opacity); - - /// Creates a copy of this class but with the given fields - /// replaced with the new values. - MapBubbleSettings _copyWith({ - double minRadius, - double maxRadius, - Color color, - double opacity, - double strokeWidth, - Color strokeColor, - }) { - return MapBubbleSettings( - minRadius: minRadius ?? this.minRadius, - maxRadius: maxRadius ?? this.maxRadius, - color: color ?? this.color, - opacity: opacity ?? this.opacity, - strokeWidth: strokeWidth ?? this.strokeWidth, - strokeColor: strokeColor ?? this.strokeColor, - ); - } -} - -/// Customizes the appearance of the selected shape. -/// -/// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return SfMaps( -/// layers: [ -/// MapShapeLayer( -/// enableSelection: true, -/// selectionSettings: MapSelectionSettings( -/// color: Colors.black -/// ), -/// delegate: MapShapeLayerDelegate( -/// shapeFile: "assets/world_map.json", -/// shapeDataField: "name", -/// dataCount: bubbleData.length, -/// primaryValueMapper: (index) { -/// return bubbleData[index].country; -/// }), -/// ) -/// ], -/// ); -/// } -/// ``` -@immutable -class MapSelectionSettings { - /// Creates a [MapSelectionSettings]. - const MapSelectionSettings( - {this.color, this.strokeColor, this.strokeWidth, this.opacity = 1.0}); - - /// Fills the selected shape by this color. - /// - /// This snippet shows how to set selection color in [SfMaps]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// enableSelection: true, - /// selectionSettings: MapSelectionSettings( - /// color: Colors.black - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// See also: - /// * [strokeColor], to set stroke color for selected shape. - final Color color; - - /// Applies stroke color for the selected shape. - /// - /// This snippet shows how to set stroke color for the selected shape. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// enableSelection: true, - /// selectionSettings: MapSelectionSettings( - /// strokeColor: Colors.white - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// See also: - /// * [Color], to set selected shape color. - final Color strokeColor; - - /// Stroke width which applies to the selected shape. - /// - /// This snippet shows how to set stroke width for the selected shape. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// enableSelection: true, - /// selectionSettings: MapSelectionSettings( - /// strokeWidth: 2 - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - final double strokeWidth; - - /// Specifies the opacity of the selected shape. - /// - /// This snippet shows how to set opacity of the selected shape. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// enableSelection: true, - /// selectionSettings: MapSelectionSettings( - /// opacity: 0.5 - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - final double opacity; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - return other is MapSelectionSettings && - other.color == color && - other.strokeWidth == strokeWidth && - other.strokeColor == strokeColor && - other.opacity == opacity; - } - - @override - int get hashCode => hashValues(color, strokeWidth, strokeColor, opacity); -} - -/// Customizes the appearance of the bubble's or shape's tooltip. -/// -/// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return SfMaps( -/// layers: [ -/// MapShapeLayer( -/// enableShapeTooltip: true, -/// tooltipSettings: MapTooltipSettings( -/// color: Colors.black -/// ), -/// delegate: MapShapeLayerDelegate( -/// shapeFile: "assets/world_map.json", -/// shapeDataField: "continent", -/// dataCount: bubbleData.length, -/// primaryValueMapper: (index) { -/// return bubbleData[index].country; -/// }), -/// ) -/// ], -/// ); -/// } -/// ``` -/// -/// See also: -/// * [MapShapeLayerDelegate.shapeTooltipTextMapper], for customizing the -/// tooltip text. -/// * [MapShapeLayer.shapeTooltipBuilder], for showing the completely -/// customized widget inside the tooltip. -@immutable -class MapTooltipSettings { - /// Creates a [MapTooltipSettings]. - const MapTooltipSettings({ - this.color, - this.textStyle, - this.strokeWidth, - this.strokeColor, - }); - - /// Fills the tooltip by this color. - /// - /// This snippet shows how to set the tooltip color in [SfMaps]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// enableShapeTooltip: true, - /// tooltipSettings: MapTooltipSettings( - /// color: Colors.black - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// See also: - /// * [textStyle], for customizing the style of the tooltip text. - final Color color; - - /// Customizes the tooltip text's style. - /// - /// This snippet shows how to customize the tooltip text style in [SfMaps]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// enableShapeTooltip: true, - /// tooltipSettings: MapTooltipSettings( - /// textStyle: TextStyle(color: Colors.red) - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - final TextStyle textStyle; - - /// Specifies the stroke width applies to the tooltip. - /// - /// This snippet shows how to customize the stroke width in [SfMaps]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// enableShapeTooltip: true, - /// tooltipSettings: MapTooltipSettings( - /// strokeWidth: 2 - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// See also: - /// * [strokeColor], for customizing the stroke color of the tooltip. - final double strokeWidth; - - /// Specifies the stroke color applies to the tooltip. - /// - /// This snippet shows how to customize stroke color in [SfMaps]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// enableShapeTooltip: true, - /// tooltipSettings: MapTooltipSettings( - /// strokeColor: Colors.white - /// ), - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// - /// See also: - /// * [strokeWidth] for customizing the stroke width of the tooltip. - final Color strokeColor; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - return other is MapTooltipSettings && - other.color == color && - other.strokeWidth == strokeWidth && - other.strokeColor == strokeColor && - other.textStyle == textStyle; - } - - @override - int get hashCode => hashValues(color, strokeWidth, strokeColor, textStyle); -} - -/// Provides options for customizing the appearance of the toolbar in the web -/// platform. -class MapToolbarSettings { - /// Creates a [MapToolbarSettings]. - const MapToolbarSettings({ - this.iconColor, - this.itemBackgroundColor, - this.itemHoverColor, - this.direction = Axis.horizontal, - this.position = MapToolbarPosition.topRight, - }); - - /// Specifies the color applies to the tooltip icons. - final Color iconColor; - - /// Specifies the color applies to the tooltip icon's background. - final Color itemBackgroundColor; - - /// Specifies the color applies to the tooltip icon's background on hovering. - final Color itemHoverColor; - - /// Arranges the toolbar items in either horizontal or vertical direction. - /// - /// Defaults to [Axis.horizontal]. - final Axis direction; - - /// Option to position the toolbar in all the corners of the maps. - /// - /// Defaults to [MapToolbarPosition.topRight]. - final MapToolbarPosition position; -} - -/// Creating MapModel class for adding map data in it. -class _MapModel { - _MapModel({ - this.primaryKey, - this.pixelPoints, - this.rawPoints, - this.actualIndex, - this.dataIndex, - this.legendMapperIndex, - this.shapePath, - this.isSelected = false, - this.shapeColor, - this.dataLabelText, - this.visibleDataLabelText, - this.bubbleColor, - this.bubbleSizeValue, - this.bubbleRadius, - this.bubblePath, - this.tooltipText, - }); - - /// Contains [sourceDataPath] values. - final String primaryKey; - - /// Contains pixel points. - List> pixelPoints; - - /// Contains coordinate points. - final List> rawPoints; - - /// Contains data Source index. - int dataIndex; - - /// Contains the shape path. - Path shapePath; - - /// Option to select the particular shape - /// based on the position of the tap or click. - /// - /// Returns `true` when the shape is selected else it will be `false`. - /// - /// See also: - /// * [MapSelectionSettings] option to customize the shape selection. - bool isSelected = false; - - /// Contains data source color. - Color shapeColor; - - /// Contains the actual shape color value. - dynamic shapeColorValue; - - /// Contains data source Label text. - String dataLabelText; - - /// Use this text while zooming. - String visibleDataLabelText; - - /// Specifies an item index in the data source list. - int actualIndex; - - /// Contains the index of the shape or bubble legend. - int legendMapperIndex; - - /// Contains data source bubble color. - Color bubbleColor; - - /// Contains data source bubble size. - double bubbleSizeValue; - - /// Contains data source bubble radius. - double bubbleRadius; - - /// Contains the bubble path. - Path bubblePath; - - /// Contains the tooltip text. - String tooltipText; - - // Center of shape path which is used to position the data labels. - Offset shapePathCenter; - - // Width for smart position the data labels. - double shapeWidth; - - void reset() { - dataIndex = null; - dataLabelText = null; - shapeColor = null; - shapeColorValue = null; - bubbleColor = null; - bubbleRadius = null; - bubblePath = null; - bubbleSizeValue = null; - isSelected = false; - tooltipText = null; - } -} - -class _ShapeBounds { - _ShapeBounds( - {this.minLongitude, - this.minLatitude, - this.maxLongitude, - this.maxLatitude}); - - num minLongitude; - - num minLatitude; - - num maxLongitude; - - num maxLatitude; - - _ShapeBounds get empty => _ShapeBounds( - minLongitude: null, - minLatitude: null, - maxLongitude: null, - maxLatitude: null); -} - -class _ShapeFileData { - Map decodedJsonData; - - Map source; - - _ShapeBounds bounds; - - void reset() { - decodedJsonData?.clear(); - source?.clear(); - bounds = bounds?.empty; - } -} - -class _ShapeLayerChildRenderBoxBase extends RenderProxyBox { - // Invoked when a pointer has stopped, - // contacting the screen at a particular location. - void onTap(Offset position, {_MapModel model, _Layer layer}) { - // Override it, if the child of _ShapeLayer need to handle tap event. - } - - // Invoked when a mouse pointer has moved onto or within the region, - // without buttons pressed. - void onHover(Offset position, {_MapModel model, _Layer layer}) { - // Override it, if the child of _ShapeLayer need to handle hover event. - } - - /// Invoked when a mouse pointer has exited from the region. - void onExit() { - // Override it, if the child of _ShapeLayer need to handle exit event. - } - - void refresh() { - // Override it, if the child of _ShapeLayer need to handle end interaction. - } -} diff --git a/packages/syncfusion_flutter_maps/lib/src/common/utils.dart b/packages/syncfusion_flutter_maps/lib/src/common/utils.dart deleted file mode 100644 index ddacec817..000000000 --- a/packages/syncfusion_flutter_maps/lib/src/common/utils.dart +++ /dev/null @@ -1,287 +0,0 @@ -part of maps; - -Future<_ShapeFileData> _retrieveDataFromShapeFile( - String file, - String shapeDataField, - _ShapeFileData shapeFileData, - bool isShapeFileDecoded) async { - if (isShapeFileDecoded) { - return shapeFileData; - } - final String assertBundleData = await rootBundle.loadString(file); - final Map data = { - 'AssertBundleData': assertBundleData, - 'ShapeDataField': shapeDataField, - 'ShapeFileData': shapeFileData - }; - return compute(_decodeJsonData, data); -} - -/// Returns the URL template in the required format for the Bing Maps. -/// -/// For Bing maps, an additional step is required. The format of the required -/// URL varies from the other tile services. Hence, we have added this top-level -/// method which returns the URL in the required format. -/// -/// You can use the URL template returned from this method to pass it to the -/// [MapTileLayer.urlTemplate] property. -/// -/// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return FutureBuilder( -/// future: getBingUrlTemplate( -/// 'http://dev.virtualearth.net/REST/V1/Imagery/Metadata/Road -/// OnDemand?output=json&include=ImageryProviders&key=YOUR_KEY'), -/// builder: (context, snapshot) { -/// if (snapshot.hasData) { -/// return SfMaps( -/// layers: [ -/// MapTileLayer( -/// initialFocalLatLng: MapLatLng(20.5937, 78.9629), -/// zoomPanBehavior: MapZoomPanBehavior(), -/// initialZoomLevel: 3, -/// urlTemplate: snapshot.data, -/// ), -/// ], -/// ); -/// } -/// return CircularProgressIndicator(); -/// }); -/// } -/// ``` -Future getBingUrlTemplate(String url) async { - final http.Response response = await _fetchResponse(url); - assert(response.statusCode == 200, 'Invalid key'); - if (response.statusCode == 200) { - final Map decodedJson = json.decode(response.body); - String imageUrl; - String imageUrlSubDomains; - if (decodedJson['authenticationResultCode'] == 'ValidCredentials') { - for (final key in decodedJson.keys) { - if (key == 'resourceSets') { - final List resourceSets = decodedJson[key]; - for (final key in resourceSets[0].keys) { - if (key == 'resources') { - final List resources = (resourceSets[0])[key]; - final Map resourcesMap = resources[0]; - imageUrl = resourcesMap['imageUrl']; - final List subDomains = - resourcesMap['imageUrlSubdomains']; - imageUrlSubDomains = subDomains[0]; - break; - } - } - break; - } - } - - final List splitUrl = imageUrl.split('{subdomain}'); - return splitUrl[0] + imageUrlSubDomains + splitUrl[1]; - } - } - return null; -} - -Future _fetchResponse(String url) { - return http.get(url); -} - -_ShapeFileData _decodeJsonData(Map data) { - data['ShapeFileData'].decodedJsonData = jsonDecode(data['AssertBundleData']); - _readJsonFile(data); - return data['ShapeFileData']; -} - -void _readJsonFile(Map data) { - final _ShapeFileData shapeFileData = data['ShapeFileData']; - final String shapeDataField = data['ShapeDataField']; - - List polygonGeometryData; - int multipolygonGeometryLength; - Map geometry; - Map properties; - final bool hasFeatures = - shapeFileData.decodedJsonData.containsKey('features'); - final bool hasGeometries = - shapeFileData.decodedJsonData.containsKey('geometries'); - final String key = hasFeatures - ? 'features' - : hasGeometries - ? 'geometries' - : null; - final int jsonLength = - key.isEmpty ? 0 : shapeFileData.decodedJsonData[key].length; - - for (int i = 0; i < jsonLength; i++) { - if (hasFeatures) { - final dynamic features = shapeFileData.decodedJsonData[key][i]; - geometry = features['geometry']; - properties = features['properties']; - } else if (hasGeometries) { - geometry = shapeFileData.decodedJsonData[key][i]; - } - - if (geometry['type'] == 'Polygon') { - polygonGeometryData = geometry['coordinates'][0]; - _updateMapDataSource( - shapeFileData, shapeDataField, properties, polygonGeometryData); - } else { - multipolygonGeometryLength = geometry['coordinates'].length; - for (int j = 0; j < multipolygonGeometryLength; j++) { - polygonGeometryData = geometry['coordinates'][j][0]; - _updateMapDataSource( - shapeFileData, shapeDataField, properties, polygonGeometryData); - } - } - } -} - -void _updateMapDataSource(_ShapeFileData shapeFileData, String shapeDataField, - Map properties, List points) { - final String dataPath = - properties != null ? properties[shapeDataField] : null; - shapeFileData.source.update( - dataPath, - (_MapModel model) { - model.rawPoints.add(points); - return model; - }, - ifAbsent: () { - final int dataSourceIndex = shapeFileData.source.length; - return _MapModel( - primaryKey: dataPath, - actualIndex: dataSourceIndex, - legendMapperIndex: dataSourceIndex, - rawPoints: >[points], - ); - }, - ); - - _updateShapeBounds(shapeFileData, points); -} - -void _updateShapeBounds( - _ShapeFileData shapeFileData, List coordinates) { - List data; - num longitude, latitude; - final int length = coordinates.length; - for (int i = 0; i < length; i++) { - data = coordinates[i]; - longitude = data[0]; - latitude = data[1]; - if (shapeFileData.bounds.minLongitude == null) { - shapeFileData.bounds.minLongitude = longitude; - shapeFileData.bounds.minLatitude = latitude; - shapeFileData.bounds.maxLongitude = longitude; - shapeFileData.bounds.maxLatitude = latitude; - } else { - shapeFileData.bounds.minLongitude = - min(longitude, shapeFileData.bounds.minLongitude); - shapeFileData.bounds.minLatitude = - min(latitude, shapeFileData.bounds.minLatitude); - shapeFileData.bounds.maxLongitude = - max(longitude, shapeFileData.bounds.maxLongitude); - shapeFileData.bounds.maxLatitude = - max(latitude, shapeFileData.bounds.maxLatitude); - } - } -} - -Size _getBoxSize(BoxConstraints constraints) { - final double width = constraints.hasBoundedWidth ? constraints.maxWidth : 300; - final double height = - constraints.hasBoundedHeight ? constraints.maxHeight : 300; - return Size(width, height); -} - -Offset _pixelFromLatLng(num latitude, num longitude, Size size, - [Offset offset = const Offset(0, 0), double factor = 1.0]) { - assert(latitude != null); - assert(longitude != null); - assert(size != null); - - final double x = (longitude + 180.0) / 360.0; - final double sinLatitude = sin(latitude * pi / 180.0); - final double y = - 0.5 - log((1.0 + sinLatitude) / (1.0 - sinLatitude)) / (4.0 * pi); - final double mapSize = size.longestSide * factor; - final double dx = offset.dx + _clip(x * mapSize + 0.5, 0.0, mapSize - 1); - final double dy = offset.dy + _clip(y * mapSize + 0.5, 0.0, mapSize - 1); - return Offset(dx, dy); -} - -MapLatLng _pixelToLatLng( - Offset offset, Size size, Offset translation, double factor) { - final double mapSize = size.longestSide * factor; - final double x = - (_clip(offset.dx - translation.dx, 0, mapSize - 1) / mapSize) - 0.5; - final double y = - 0.5 - (_clip(offset.dy - translation.dy, 0, mapSize - 1) / mapSize); - final double latitude = 90 - 360 * atan(exp(-y * 2 * pi)) / pi; - final double longitude = 360 * x; - return MapLatLng(latitude, longitude); -} - -// Get boundary value. -double _clip(double value, double minValue, double maxValue) { - return min(max(value, minValue), maxValue); -} - -double _interpolateValue(double value, double min, double max) { - assert(min != null); - max ??= value; - if (value > max) { - value = max; - } else if (value < min) { - value = min; - } - return value; -} - -double _getActualCircleRadius(double circleRadius, double strokeWidth) { - return strokeWidth > circleRadius - ? circleRadius / 2 - : circleRadius - strokeWidth / 2; -} - -Offset _getPixelFromLatLng(MapLatLng latLng, double scale) { - final double _minLatitude = -85.05112878; - final double _maxLatitude = 85.05112878; - final double _minLongitude = -180; - final double _maxLongitude = 180; - - final double latitude = _clip(latLng.latitude, _minLatitude, _maxLatitude); - final double longitude = - _clip(latLng.longitude, _minLongitude, _maxLongitude); - final double x = (longitude + 180) / 360; - final double sinLatitude = sin(latitude * pi / 180); - final double y = 0.5 - log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * pi); - - final double tileSize = _getTotalTileWidth(scale); - final double pixelX = _clip(x * tileSize + 0.5, 0, tileSize - 1); - final double pixelY = _clip(y * tileSize + 0.5, 0, tileSize - 1); - return Offset(pixelX, pixelY); -} - -MapLatLng _getLatLngFromPixel(Offset point, {double scale}) { - final double tileSize = _getTotalTileWidth(scale); - final double x = (_clip(point.dx, 0, tileSize - 1) / tileSize) - 0.5; - final double y = 0.5 - (_clip(point.dy, 0, tileSize - 1) / tileSize); - - final double latitude = 90 - 360 * atan(exp(-y * 2 * pi)) / pi; - final double longitude = 360 * x; - return MapLatLng(latitude, longitude); -} - -double _getTotalTileWidth(double zoom) { - return 256 * pow(2, zoom); -} - -// Default hover color opacity value. -const double _hoverColorOpacity = 0.7; - -// If shape color opacity is same hover color opacity, -// hover is not visible in UI. -// So need to decrease hover opacity -const double _minHoverOpacity = 0.5; diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/default_controller.dart b/packages/syncfusion_flutter_maps/lib/src/controller/default_controller.dart similarity index 68% rename from packages/syncfusion_flutter_maps/lib/src/layer/default_controller.dart rename to packages/syncfusion_flutter_maps/lib/src/controller/default_controller.dart index 6c451f8ab..e54920ddc 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/default_controller.dart +++ b/packages/syncfusion_flutter_maps/lib/src/controller/default_controller.dart @@ -1,5 +1,17 @@ -part of maps; +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 '../behavior/zoom_pan_behavior.dart'; +import '../elements/shapes.dart'; +import '../layer/tile_layer.dart'; +import '../utils.dart'; + +// ignore_for_file: public_member_api_docs typedef _ZoomingCallback = void Function(MapZoomDetails details); typedef _PanningCallback = void Function(MapPanDetails details); @@ -8,7 +20,7 @@ typedef _ZoomToCallback = void Function(double factor, {MapLatLng latlng}); typedef _PanToCallback = void Function(MapLatLng latlng); -class _DefaultController { +class MapController { final List _toggledIndices = []; List get toggledIndices => _toggledIndices; ObserverList _listeners = ObserverList(); @@ -16,17 +28,19 @@ class _DefaultController { ObserverList<_ZoomingCallback>(); ObserverList<_PanningCallback> _panningListeners = ObserverList<_PanningCallback>(); + ObserverList _refreshListeners = ObserverList(); ObserverList _resetListeners = ObserverList(); ObserverList _toggledListeners = ObserverList(); _ZoomToCallback onZoomLevelChange; _PanToCallback onPanChange; + GlobalKey tooltipKey; Size shapeLayerBoxSize; double shapeLayerSizeFactor = 1.0; Offset shapeLayerOffset; - Offset shapeLayerOrigin; - _Gesture gesture; + Offset shapeLayerOrigin = Offset.zero; + Gesture gesture; MapLatLng visibleFocalLatLng; Rect visibleBounds; MapLatLngBounds visibleLatLngBounds; @@ -34,8 +48,14 @@ class _DefaultController { bool isInInteractive = false; Offset pinchCenter = Offset.zero; Offset panDistance = Offset.zero; - Offset adjustment = Offset.zero; + Offset normalize = Offset.zero; Rect currentBounds = Rect.zero; + double tileLayerZoomLevel; + MapZoomLevel tileCurrentLevelDetails; + Size totalTileSize; + Size tileLayerBoxSize; + bool isTileLayerChild = false; + double tileLayerLocalScale = 1.0; // Stores the index of current tapped item which is used // to identify whether the legend item is toggled or un-toggled. @@ -66,7 +86,7 @@ class _DefaultController { } } - bool wasToggled(_MapModel model) { + bool wasToggled(MapModel model) { return toggledIndices.contains(model.legendMapperIndex); } @@ -94,6 +114,14 @@ class _DefaultController { _resetListeners.remove(listener); } + void addRefreshListener(VoidCallback listener) { + _refreshListeners.add(listener); + } + + void removeRefreshListener(VoidCallback listener) { + _refreshListeners.remove(listener); + } + void addListener(VoidCallback listener) { _listeners.add(listener); } @@ -120,6 +148,12 @@ class _DefaultController { } } + void notifyRefreshListeners() { + for (final VoidCallback listener in _refreshListeners) { + listener(); + } + } + void notifyListeners() { for (final VoidCallback listener in _listeners) { listener(); @@ -138,7 +172,7 @@ class _DefaultController { MapLatLng getVisibleFocalLatLng([Offset translation, double factor]) { factor ??= shapeLayerSizeFactor; translation ??= shapeLayerOffset; - return _pixelToLatLng( + return pixelToLatLng( Offset(shapeLayerBoxSize.width / 2, shapeLayerBoxSize.height / 2), shapeLayerBoxSize, translation, @@ -151,7 +185,7 @@ class _DefaultController { translation ??= shapeLayerOffset; focalLatLng ??= getVisibleFocalLatLng(translation, factor); return Rect.fromCenter( - center: _pixelFromLatLng( + center: pixelFromLatLng( visibleFocalLatLng.latitude, visibleFocalLatLng.longitude, shapeLayerBoxSize, @@ -166,8 +200,8 @@ class _DefaultController { 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( @@ -184,25 +218,38 @@ class _DefaultController { return previousOrigin + Offset(dx, dy); } - void applyTransform(PaintingContext context, Offset offset) { - switch (gesture) { - case _Gesture.scale: - // Translating to the focal point - // which we got from [ScaleUpdateDetails.localFocalPoint]. - context.canvas - ..translate(offset.dx + pinchCenter.dx + adjustment.dx, - offset.dy + pinchCenter.dy + adjustment.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); - break; - default: - context.canvas - .translate(offset.dx + adjustment.dx, offset.dy + adjustment.dy); + Size getTileSize({double zoomLevel}) { + zoomLevel ??= tileLayerZoomLevel; + return Size.square(256 * pow(2, zoomLevel)); + } + + void applyTransform(PaintingContext context, Offset offset, + [bool isVectorLayer = false]) { + if (isVectorLayer && tileCurrentLevelDetails != null) { + context.canvas + ..translate(offset.dx + tileCurrentLevelDetails.translatePoint.dx, + offset.dy + tileCurrentLevelDetails.translatePoint.dy) + ..scale(tileCurrentLevelDetails.scale); + } else { + switch (gesture) { + case Gesture.scale: + // 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) + ..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); + break; + default: + context.canvas + .translate(offset.dx + normalize.dx, offset.dy + normalize.dy); + } } } @@ -212,5 +259,6 @@ class _DefaultController { _zoomingListeners = null; _panningListeners = null; _resetListeners = null; + _refreshListeners = null; } } diff --git a/packages/syncfusion_flutter_maps/lib/src/controller/shape_layer_controller.dart b/packages/syncfusion_flutter_maps/lib/src/controller/shape_layer_controller.dart new file mode 100644 index 000000000..3471bd7ac --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/controller/shape_layer_controller.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; + +import '../utils.dart'; + +typedef _ListenerEntry = void Function( + [MarkerAction action, List indices]); + +/// Base class of [MapShapeLayerController] and [MapTileLayerController]. +abstract class MapLayerController extends ChangeNotifier { + ObserverList<_ListenerEntry> _listeners = ObserverList<_ListenerEntry>(); + + /// Adds marker dynamically in the provided index. + /// + /// If the [MapShapeLayer.initialMarkersCount] is 10 and if the index given + /// for the [insertMarker] method is 10 which is greater than the available + /// indices, then the marker will be added as a last item. + /// + /// See also: + /// * [MapShapeLayer.markerBuilder], to return the [MapMarker]. + void insertMarker(int index) { + _notifyMarkerListeners(MarkerAction.insert, [index]); + } + + /// Removes the marker in the provided index. + void removeMarkerAt(int index) { + _notifyMarkerListeners(MarkerAction.removeAt, [index]); + } + + /// Updates the markers in the given indices dynamically. + /// See also: + /// * [MapShapeLayer.markerBuilder], to return the updated [MapMarker]. + void updateMarkers(List indices) { + _notifyMarkerListeners(MarkerAction.replace, indices); + } + + /// Clears all the markers. + void clearMarkers() { + _notifyMarkerListeners(MarkerAction.clear); + } + + /// Call all the registered listeners. + void _notifyMarkerListeners([MarkerAction action, List indices]) { + for (final _ListenerEntry listener in _listeners) { + listener(action, indices); + } + } + + @override + void addListener(Object listener) { + if (listener is _ListenerEntry) { + _listeners.add(listener); + } else { + super.addListener(listener); + } + } + + @override + void removeListener(Object listener) { + if (listener is _ListenerEntry) { + _listeners.remove(listener); + } else { + super.addListener(listener); + } + } + + @override + void dispose() { + _listeners = null; + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_maps/lib/src/features/maps_bubble.dart b/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart similarity index 60% rename from packages/syncfusion_flutter_maps/lib/src/features/maps_bubble.dart rename to packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart index ae6a8fa03..831958737 100644 --- a/packages/syncfusion_flutter_maps/lib/src/features/maps_bubble.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart @@ -1,71 +1,111 @@ -part of maps; - -/// A Render object widget which draws bubble shape. -class _MapBubble extends LeafRenderObjectWidget { - const _MapBubble({ - this.delegate, +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../behavior/zoom_pan_behavior.dart'; +import '../controller/default_controller.dart'; +import '../elements/legend.dart'; +import '../enum.dart'; +import '../layer/shape_layer.dart'; +import '../settings.dart'; +import '../utils.dart'; +import 'shapes.dart'; + +// ignore_for_file: public_member_api_docs +class MapBubble extends LeafRenderObjectWidget { + const MapBubble({ + Key key, + this.source, this.mapDataSource, this.bubbleSettings, - this.legendSettings, + this.legend, + this.showDataLabels, this.themeData, - this.defaultController, - this.state, - }); - - final MapShapeLayerDelegate delegate; - final Map mapDataSource; + this.controller, + this.bubbleAnimationController, + this.dataLabelAnimationController, + this.toggleAnimationController, + this.hoverBubbleAnimationController, + }) : super(key: key); + + final MapShapeSource source; + final Map mapDataSource; final MapBubbleSettings bubbleSettings; - final MapLegendSettings legendSettings; + final MapLayerLegend legend; + final bool showDataLabels; final SfMapsThemeData themeData; - final _DefaultController defaultController; - final _MapsShapeLayerState state; + final MapController controller; + final AnimationController bubbleAnimationController; + final AnimationController dataLabelAnimationController; + final AnimationController toggleAnimationController; + final AnimationController hoverBubbleAnimationController; @override - _RenderMapBubble createRenderObject(BuildContext context) { - return _RenderMapBubble( - delegate: delegate, - mapDataSource: mapDataSource, - bubbleSettings: bubbleSettings, - legendSettings: legendSettings, - themeData: themeData, - defaultController: defaultController, - state: state); + RenderMapBubble createRenderObject(BuildContext context) { + return RenderMapBubble( + source: source, + mapDataSource: mapDataSource, + bubbleSettings: bubbleSettings, + legend: legend, + showDataLabels: showDataLabels, + themeData: themeData, + controller: controller, + bubbleAnimationController: bubbleAnimationController, + dataLabelAnimationController: dataLabelAnimationController, + toggleAnimationController: toggleAnimationController, + hoverBubbleAnimationController: hoverBubbleAnimationController, + ); } @override - void updateRenderObject(BuildContext context, _RenderMapBubble renderObject) { + void updateRenderObject(BuildContext context, RenderMapBubble renderObject) { renderObject - ..delegate = delegate + ..source = source ..mapDataSource = mapDataSource ..bubbleSettings = bubbleSettings - ..legendSettings = legendSettings - ..defaultController = defaultController - ..themeData = themeData; + ..legend = legend + ..showDataLabels = showDataLabels + ..themeData = themeData + ..bubbleAnimationController = bubbleAnimationController + ..dataLabelAnimationController = dataLabelAnimationController + ..toggleAnimationController = toggleAnimationController + ..hoverBubbleAnimationController = hoverBubbleAnimationController; } } -class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { - _RenderMapBubble({ - MapShapeLayerDelegate delegate, - Map mapDataSource, +class RenderMapBubble extends ShapeLayerChildRenderBoxBase { + RenderMapBubble({ + MapShapeSource source, + Map mapDataSource, MapBubbleSettings bubbleSettings, - MapLegendSettings legendSettings, + MapLayerLegend legend, + bool showDataLabels, SfMapsThemeData themeData, - _DefaultController defaultController, - _MapsShapeLayerState state, - }) : mapDataSource = mapDataSource, - _delegate = delegate, + MapController controller, + AnimationController bubbleAnimationController, + AnimationController dataLabelAnimationController, + AnimationController toggleAnimationController, + AnimationController hoverBubbleAnimationController, + }) : _source = source, + mapDataSource = mapDataSource, _bubbleSettings = bubbleSettings, - _legendSettings = legendSettings, + _legend = legend, + showDataLabels = showDataLabels, _themeData = themeData, - defaultController = defaultController, - _state = state { + controller = controller, + bubbleAnimationController = bubbleAnimationController, + dataLabelAnimationController = dataLabelAnimationController, + toggleAnimationController = toggleAnimationController, + hoverBubbleAnimationController = hoverBubbleAnimationController { _bubbleAnimation = CurvedAnimation( - parent: _state.bubbleAnimationController, + parent: bubbleAnimationController, curve: const Interval(0.2, 1.0, curve: Curves.easeInOut), ); _toggleBubbleAnimation = CurvedAnimation( - parent: _state.toggleAnimationController, curve: Curves.easeInOut); + parent: toggleAnimationController, curve: Curves.easeInOut); _forwardToggledBubbleColorTween = ColorTween(); _forwardToggledBubbleStrokeColorTween = ColorTween(); @@ -73,7 +113,7 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { _reverseToggledBubbleStrokeColorTween = ColorTween(); _toggleBubbleAnimation = CurvedAnimation( - parent: _state.toggleAnimationController, curve: Curves.easeInOut); + parent: toggleAnimationController, curve: Curves.easeInOut); _forwardBubbleHoverColorTween = ColorTween(); _forwardBubbleHoverStrokeColorTween = ColorTween(); @@ -81,9 +121,9 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { _reverseBubbleHoverStrokeColorTween = ColorTween(); _hoverBubbleAnimation = CurvedAnimation( - parent: _state.hoverBubbleAnimationController, curve: Curves.easeInOut); + parent: hoverBubbleAnimationController, curve: Curves.easeInOut); - if (_legendSettings.enableToggleInteraction) { + if (_legend != null && _legend.enableToggleInteraction) { _initializeToggledBubbleTweenColors(); } @@ -94,56 +134,35 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { } } - final _MapsShapeLayerState _state; - Map mapDataSource; Animation _bubbleAnimation; Animation _toggleBubbleAnimation; Animation _hoverBubbleAnimation; - _MapModel _currentHoverItem; - _MapModel _previousHoverItem; - - // Apply color animation for the toggled bubble. The - // begin color will be bubble color and the - // end color will be toggled color. + MapModel _currentHoverItem; + MapModel _previousHoverItem; ColorTween _forwardToggledBubbleColorTween; - // Apply stroke color animation for the toggled bubble. The - // begin color will be bubble stroke color and the - // end color will be toggled bubble stroke color. ColorTween _forwardToggledBubbleStrokeColorTween; - // Apply color animation for the bubble while un-toggling the toggled bubble. - // The begin color will be toggled bubble color and the - // end color will be bubble color. ColorTween _reverseToggledBubbleColorTween; - // Apply stroke color animation for the bubble while un-toggling the toggled - // bubble. The begin color will be toggled bubble stroke color and the - // end color will be bubble stroke color. ColorTween _reverseToggledBubbleStrokeColorTween; - // Apply color animation for the hover bubble. The - // begin color will be bubble color and the - // end color will be hover color. ColorTween _forwardBubbleHoverColorTween; - // Apply stroke color animation for the hover bubble. The - // begin color will be bubble stroke color and the - // end color will be hover bubble stroke color. ColorTween _forwardBubbleHoverStrokeColorTween; - // Apply color animation for the bubble while exist the hovered bubble. - // The begin color will be hover bubble color and the - // end color will be bubble color. ColorTween _reverseBubbleHoverColorTween; - // Apply stroke color animation for the bubble while exist the hovered bubble. - // bubble. The begin color will be hover bubble stroke color and the - // end color will be bubble stroke color. ColorTween _reverseBubbleHoverStrokeColorTween; - _DefaultController defaultController; - - MapShapeLayerDelegate get delegate => _delegate; - MapShapeLayerDelegate _delegate; - set delegate(MapShapeLayerDelegate value) { - if (_delegate == value) { + Map mapDataSource; + bool showDataLabels; + MapController controller; + AnimationController bubbleAnimationController; + AnimationController dataLabelAnimationController; + AnimationController toggleAnimationController; + AnimationController hoverBubbleAnimationController; + + MapShapeSource get source => _source; + MapShapeSource _source; + set source(MapShapeSource value) { + if (_source == value) { return; } - _delegate = value; + _source = value; _previousHoverItem = null; markNeedsPaint(); } @@ -166,7 +185,7 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { return; } _themeData = value; - if (_legendSettings.enableToggleInteraction) { + if (_legend != null && _legend.enableToggleInteraction) { _initializeToggledBubbleTweenColors(); } @@ -179,17 +198,17 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { markNeedsPaint(); } - MapLegendSettings get legendSettings => _legendSettings; - MapLegendSettings _legendSettings; - set legendSettings(MapLegendSettings value) { - // Update [MapsShapeLayer.legendSettings] value only when + MapLayerLegend get legend => _legend; + MapLayerLegend _legend; + set legend(MapLayerLegend value) { + // Update [MapsShapeLayer.legend] value only when // [MapsShapeLayer.legend] property is set to bubble. - if (_state.widget.legendSource != MapElement.bubble || - _legendSettings == value) { + if (_legend != null && _legend.source != MapElement.bubble || + _legend == value) { return; } - _legendSettings = value; - if (_legendSettings.enableToggleInteraction) { + _legend = value; + if (_legend.enableToggleInteraction) { _initializeToggledBubbleTweenColors(); } @@ -206,9 +225,10 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { _themeData.bubbleHoverStrokeColor != Colors.transparent; bool get hasToggledStroke => - _legendSettings.toggledItemStrokeWidth > 0 && - _legendSettings.toggledItemStrokeColor != null && - _legendSettings.toggledItemStrokeColor != Colors.transparent; + _legend != null && + _legend.toggledItemStrokeWidth > 0 && + _legend.toggledItemStrokeColor != null && + _legend.toggledItemStrokeColor != Colors.transparent; void _handleZooming(MapZoomDetails details) { if (_currentHoverItem != null) { @@ -224,6 +244,10 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { markNeedsPaint(); } + void _handleRefresh() { + markNeedsPaint(); + } + void _handleReset() { markNeedsPaint(); } @@ -232,18 +256,13 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { bool get isRepaintBoundary => true; @override - void refresh() { - markNeedsPaint(); - } - - @override - void onHover(Offset hoverPosition, {_MapModel model, _Layer layer}) { - if (layer == _Layer.bubble && _currentHoverItem != model) { + void onHover(MapModel item, MapLayerElement element) { + if (element == MapLayerElement.bubble && _currentHoverItem != item) { _previousHoverItem = _currentHoverItem; - _currentHoverItem = model; + _currentHoverItem = item; _updateHoverItemTween(); - } else if ((_currentHoverItem != null && _currentHoverItem != model) || - (layer == _Layer.shape && _currentHoverItem == model)) { + } else if ((_currentHoverItem != null && _currentHoverItem != item) || + (element == MapLayerElement.shape && _currentHoverItem == item)) { onExit(); } } @@ -256,7 +275,8 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { } void _updateHoverItemTween() { - final double opacity = _bubbleAnimation.value * _bubbleSettings.opacity; + final double opacity = + _bubbleAnimation.value * _bubbleSettings.color.opacity; final Color defaultColor = bubbleSettings.color.withOpacity(opacity); if (_currentHoverItem != null) { _forwardBubbleHoverColorTween.begin = @@ -272,77 +292,87 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { _previousHoverItem.bubbleColor ?? defaultColor; } - _state.hoverBubbleAnimationController.forward(from: 0.0); + hoverBubbleAnimationController.forward(from: 0.0); } - Color _getHoverFillColor( - double opacity, Color defaultColor, _MapModel model) { + Color _getHoverFillColor(double opacity, Color defaultColor, MapModel model) { final Color bubbleColor = model.bubbleColor ?? defaultColor; final bool canAdjustHoverOpacity = (model.bubbleColor != null && double.parse(model.bubbleColor.opacity.toStringAsFixed(2)) != - _hoverColorOpacity) || - _bubbleSettings.opacity != _hoverColorOpacity; + hoverColorOpacity) || + _bubbleSettings.color.opacity != hoverColorOpacity; return _themeData.bubbleHoverColor != null && _themeData.bubbleHoverColor != Colors.transparent ? _themeData.bubbleHoverColor : bubbleColor.withOpacity( - canAdjustHoverOpacity ? _hoverColorOpacity : _minHoverOpacity); + canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); } @override void performLayout() { - size = _getBoxSize(constraints); + size = getBoxSize(constraints); } @override void attach(PipelineOwner owner) { super.attach(owner); - _bubbleAnimation.addListener(markNeedsPaint); - _bubbleAnimation.addStatusListener(_handleAnimationStatusChange); - _state.toggleAnimationController?.addListener(markNeedsPaint); - _state.hoverBubbleAnimationController?.addListener(markNeedsPaint); - if (defaultController != null) { - defaultController.addToggleListener(_handleToggleChange); - defaultController.addZoomingListener(_handleZooming); - defaultController.addPanningListener(_handlePanning); - defaultController.addResetListener(_handleReset); + _bubbleAnimation + ..addListener(markNeedsPaint) + ..addStatusListener(_handleAnimationStatusChange); + toggleAnimationController?.addListener(markNeedsPaint); + hoverBubbleAnimationController?.addListener(markNeedsPaint); + if (controller == null) { + final RenderShapeLayer shapeLayerRenderBox = parent; + controller = shapeLayerRenderBox.controller; + } + + if (controller != null) { + controller + ..addToggleListener(_handleToggleChange) + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addRefreshListener(_handleRefresh) + ..addResetListener(_handleReset); } } void _handleAnimationStatusChange(AnimationStatus status) { - if (status == AnimationStatus.completed && _state.widget.showDataLabels) { - _state.dataLabelAnimationController?.forward(); + if (status == AnimationStatus.completed && showDataLabels) { + dataLabelAnimationController?.forward(); } } @override void detach() { - _bubbleAnimation.removeListener(markNeedsPaint); - _bubbleAnimation.removeStatusListener(_handleAnimationStatusChange); - _state.toggleAnimationController?.removeListener(markNeedsPaint); - _state.hoverBubbleAnimationController?.removeListener(markNeedsPaint); - if (defaultController != null) { - defaultController.removeToggleListener(_handleToggleChange); - defaultController.removeZoomingListener(_handleZooming); - defaultController.removePanningListener(_handlePanning); - defaultController.removeResetListener(_handleReset); + _bubbleAnimation + ..removeListener(markNeedsPaint) + ..removeStatusListener(_handleAnimationStatusChange); + toggleAnimationController?.removeListener(markNeedsPaint); + hoverBubbleAnimationController?.removeListener(markNeedsPaint); + if (controller != null) { + controller + ..removeToggleListener(_handleToggleChange) + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeRefreshListener(_handleRefresh) + ..removeResetListener(_handleReset); } + super.detach(); } void _handleToggleChange() { - if (_state.widget.legendSource == MapElement.bubble) { + if (legend.source == MapElement.bubble) { _updateToggledBubbleTweenColor(); - _state.toggleAnimationController.forward(from: 0); + toggleAnimationController.forward(from: 0); } } void _initializeToggledBubbleTweenColors() { - final Color toggledBubbleColor = - _themeData.toggledItemColor != Colors.transparent - ? _themeData.toggledItemColor - .withOpacity(_legendSettings.toggledItemOpacity) - : null; + final Color toggledBubbleColor = _themeData.toggledItemColor != + Colors.transparent + ? _themeData.toggledItemColor.withOpacity(_legend.toggledItemOpacity) + : null; _forwardToggledBubbleColorTween.end = toggledBubbleColor; _forwardToggledBubbleStrokeColorTween.begin = _themeData.bubbleStrokeColor; @@ -367,33 +397,32 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { final Color bubbleStrokeColor = _bubbleSettings.strokeColor; final bool canAdjustHoverOpacity = double.parse(bubbleStrokeColor.opacity.toStringAsFixed(2)) != - _hoverColorOpacity; + hoverColorOpacity; return _themeData.bubbleHoverStrokeColor != null ?? _themeData.bubbleHoverStrokeColor != Colors.transparent ? _themeData.bubbleHoverStrokeColor : bubbleStrokeColor.withOpacity( - canAdjustHoverOpacity ? _hoverColorOpacity : _minHoverOpacity); + canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); } void _updateToggledBubbleTweenColor() { if (mapDataSource != null) { - _MapModel model; - if (_state.widget.delegate.bubbleColorMappers == null) { - model = mapDataSource.values - .elementAt(defaultController.currentToggledItemIndex); + MapModel model; + if (source.bubbleColorMappers == null) { + model = + mapDataSource.values.elementAt(controller.currentToggledItemIndex); } else { for (final mapModel in mapDataSource.values) { if (mapModel.dataIndex != null && mapModel.legendMapperIndex == - defaultController.currentToggledItemIndex) { + controller.currentToggledItemIndex) { model = mapModel; break; } } } - final Color bubbleColor = model.bubbleColor ?? - _themeData.bubbleColor.withOpacity(_bubbleSettings.opacity); + final Color bubbleColor = model.bubbleColor ?? _themeData.bubbleColor; _forwardToggledBubbleColorTween.begin = bubbleColor; _reverseToggledBubbleColorTween.end = bubbleColor; } @@ -407,18 +436,18 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { context.canvas ..save() ..clipRect(bounds); - defaultController.applyTransform(context, offset); + controller.applyTransform(context, offset); - final double opacity = _bubbleAnimation.value * _bubbleSettings.opacity; + final double opacity = + _bubbleAnimation.value * _bubbleSettings.color.opacity; final Color defaultColor = bubbleSettings.color.withOpacity(opacity); - final bool hasToggledIndices = - defaultController.toggledIndices.isNotEmpty; + final bool hasToggledIndices = controller.toggledIndices.isNotEmpty; final Paint fillPaint = Paint()..isAntiAlias = true; final Paint strokePaint = Paint() ..isAntiAlias = true ..style = PaintingStyle.stroke; - mapDataSource.forEach((String key, _MapModel model) { + mapDataSource.forEach((String key, MapModel model) { if (model.bubbleSizeValue == null || (_currentHoverItem != null && _currentHoverItem.primaryKey == model.primaryKey)) { @@ -443,43 +472,45 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { double _getDesiredValue(double value) { return value / - (defaultController.gesture == _Gesture.scale - ? defaultController.localScale - : 1); + (controller.gesture == Gesture.scale ? controller.localScale : 1); } - void _drawBubbleStroke(PaintingContext context, _MapModel model, + void _drawBubbleStroke(PaintingContext context, MapModel model, Paint strokePaint, double bubbleRadius, bool hasToggledIndices) { if (hasToggledStroke || hasDefaultStroke) { _updateStrokePaint(model, strokePaint, hasToggledIndices, bubbleRadius); - strokePaint.strokeWidth /= defaultController.gesture == _Gesture.scale - ? defaultController.localScale - : 1; + strokePaint.strokeWidth /= + controller.gesture == Gesture.scale ? controller.localScale : 1; context.canvas.drawCircle( model.shapePathCenter, - _getActualCircleRadius(bubbleRadius, strokePaint.strokeWidth), + _getDesiredRadius(bubbleRadius, strokePaint.strokeWidth), strokePaint); } } + double _getDesiredRadius(double circleRadius, double strokeWidth) { + return strokeWidth > circleRadius + ? circleRadius / 2 + : circleRadius - strokeWidth / 2; + } + // 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 (_state.widget.legendSource == MapElement.bubble) { - if (defaultController.currentToggledItemIndex == - model.legendMapperIndex) { + if (_legend != null && _legend.source == MapElement.bubble) { + if (controller.currentToggledItemIndex == model.legendMapperIndex) { // Set tween color to the bubble based on the currently tapped // legend item. If the legend item is toggled, then the // [_forwardToggledBubbleColorTween] return. If the legend item is // un-toggled, then the [_reverseToggledBubbleColorTween] return. - final Color bubbleColor = defaultController.wasToggled(model) + final Color bubbleColor = controller.wasToggled(model) ? _forwardToggledBubbleColorTween.evaluate(_toggleBubbleAnimation) : _reverseToggledBubbleColorTween.evaluate(_toggleBubbleAnimation); fillPaint.color = bubbleColor ?? Colors.transparent; return; - } else if (hasToggledIndices && defaultController.wasToggled(model)) { + } else if (hasToggledIndices && controller.wasToggled(model)) { // Set toggled color to the previously toggled bubbles. fillPaint.color = _forwardToggledBubbleColorTween.end ?? Colors.transparent; @@ -499,32 +530,31 @@ 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, + void _updateStrokePaint(MapModel model, Paint strokePaint, bool hasToggledIndices, double bubbleRadius) { strokePaint.style = PaintingStyle.stroke; - if (_state.widget.legendSource == MapElement.bubble) { - if (defaultController.currentToggledItemIndex == - model.legendMapperIndex) { + if (_legend != null && _legend.source == MapElement.bubble) { + if (controller.currentToggledItemIndex == model.legendMapperIndex) { // Set tween color to the bubble based on the currently tapped // legend item. If the legend item is toggled, then the // [_forwardToggledBubbleStrokeColorTween] return. // If the legend item is un-toggled, then the // [_reverseToggledBubbleStrokeColorTween] return. strokePaint - ..color = defaultController.wasToggled(model) + ..color = controller.wasToggled(model) ? _forwardToggledBubbleStrokeColorTween .evaluate(_toggleBubbleAnimation) : _reverseToggledBubbleStrokeColorTween .evaluate(_toggleBubbleAnimation) - ..strokeWidth = defaultController.wasToggled(model) - ? _legendSettings.toggledItemStrokeWidth + ..strokeWidth = controller.wasToggled(model) + ? _legend.toggledItemStrokeWidth : _bubbleSettings.strokeWidth; return; - } else if (hasToggledIndices && defaultController.wasToggled(model)) { + } else if (hasToggledIndices && controller.wasToggled(model)) { // Set toggled stroke color to the previously toggled bubbles. strokePaint ..color = _forwardToggledBubbleStrokeColorTween.end - ..strokeWidth = _legendSettings.toggledItemStrokeWidth; + ..strokeWidth = _legend.toggledItemStrokeWidth; return; } } @@ -559,8 +589,7 @@ class _RenderMapBubble extends _ShapeLayerChildRenderBoxBase { if (_currentHoverItem != null) { final double bubbleRadius = _getDesiredValue(_currentHoverItem.bubbleRadius); - final Color defaultColor = - bubbleSettings.color.withOpacity(_bubbleSettings.opacity); + final Color defaultColor = bubbleSettings.color; final Paint paint = Paint() ..style = PaintingStyle.fill ..color = _themeData.bubbleHoverColor != Colors.transparent diff --git a/packages/syncfusion_flutter_maps/lib/src/features/maps_data_label.dart b/packages/syncfusion_flutter_maps/lib/src/elements/data_label.dart similarity index 57% rename from packages/syncfusion_flutter_maps/lib/src/features/maps_data_label.dart rename to packages/syncfusion_flutter_maps/lib/src/elements/data_label.dart index f13b242a5..beab601f7 100644 --- a/packages/syncfusion_flutter_maps/lib/src/features/maps_data_label.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/data_label.dart @@ -1,33 +1,49 @@ -part of maps; +import 'dart:ui'; -/// A Render object widget which renders all data labels. -class _MapDataLabel extends LeafRenderObjectWidget { - const _MapDataLabel({ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../behavior/zoom_pan_behavior.dart'; +import '../controller/default_controller.dart'; +import '../enum.dart'; +import '../layer/shape_layer.dart'; +import '../settings.dart'; +import '../utils.dart'; +import 'shapes.dart'; + +// ignore_for_file: public_member_api_docs +class MapDataLabel extends LeafRenderObjectWidget { + const MapDataLabel({ + this.source, this.mapDataSource, this.settings, this.effectiveTextStyle, this.themeData, - this.defaultController, - this.state, + this.controller, + this.dataLabelAnimationController, }); - final Map mapDataSource; + final MapShapeSource source; + final Map mapDataSource; final MapDataLabelSettings settings; final TextStyle effectiveTextStyle; final SfMapsThemeData themeData; - final _DefaultController defaultController; - final _MapsShapeLayerState state; + final MapController controller; + final AnimationController dataLabelAnimationController; @override _RenderMapDataLabel createRenderObject(BuildContext context) { return _RenderMapDataLabel( + source: source, mapDataSource: mapDataSource, settings: settings, effectiveTextStyle: effectiveTextStyle, themeData: themeData, - defaultController: defaultController, + controller: controller, + dataLabelAnimationController: dataLabelAnimationController, mediaQueryData: MediaQuery.of(context), - state: state, ); } @@ -35,31 +51,34 @@ class _MapDataLabel extends LeafRenderObjectWidget { void updateRenderObject( BuildContext context, _RenderMapDataLabel renderObject) { renderObject + ..source = source ..mapDataSource = mapDataSource ..settings = settings ..effectiveTextStyle = effectiveTextStyle ..themeData = themeData - ..defaultController = defaultController + ..dataLabelAnimationController = dataLabelAnimationController ..mediaQueryData = MediaQuery.of(context); } } -class _RenderMapDataLabel extends _ShapeLayerChildRenderBoxBase { +class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { _RenderMapDataLabel({ - Map mapDataSource, + MapShapeSource source, + Map mapDataSource, MapDataLabelSettings settings, TextStyle effectiveTextStyle, SfMapsThemeData themeData, - _DefaultController defaultController, + MapController controller, + AnimationController dataLabelAnimationController, MediaQueryData mediaQueryData, - _MapsShapeLayerState state, - }) : mapDataSource = mapDataSource, + }) : source = source, + mapDataSource = mapDataSource, _settings = settings, _effectiveTextStyle = effectiveTextStyle, _themeData = themeData, - defaultController = defaultController, - _mediaQueryData = mediaQueryData, - _state = state { + controller = controller, + dataLabelAnimationController = dataLabelAnimationController, + _mediaQueryData = mediaQueryData { _effectiveTextScaleFactor = _mediaQueryData.textScaleFactor; _textPainter = TextPainter(textDirection: TextDirection.ltr) @@ -67,18 +86,22 @@ class _RenderMapDataLabel extends _ShapeLayerChildRenderBoxBase { _opacityTween = Tween(begin: 0.0, end: 1.0); _dataLabelAnimation = CurvedAnimation( - parent: _state.dataLabelAnimationController, + parent: dataLabelAnimationController, curve: const Interval(0.2, 1.0, curve: Curves.easeInOut), ); + _checkDataLabelColor(); } - final _MapsShapeLayerState _state; double _effectiveTextScaleFactor; Animation _dataLabelAnimation; Tween _opacityTween; + bool _isCustomTextColor; TextPainter _textPainter; - _DefaultController defaultController; - Map mapDataSource; + + MapShapeSource source; + Map mapDataSource; + MapController controller; + AnimationController dataLabelAnimationController; MapDataLabelSettings get settings => _settings; MapDataLabelSettings _settings; @@ -87,6 +110,7 @@ class _RenderMapDataLabel extends _ShapeLayerChildRenderBoxBase { return; } _settings = value; + _checkDataLabelColor(); markNeedsPaint(); } @@ -107,6 +131,7 @@ class _RenderMapDataLabel extends _ShapeLayerChildRenderBoxBase { return; } _themeData = value; + _checkDataLabelColor(); markNeedsPaint(); } @@ -122,33 +147,48 @@ class _RenderMapDataLabel extends _ShapeLayerChildRenderBoxBase { markNeedsPaint(); } + void _checkDataLabelColor() { + _isCustomTextColor = _settings.textStyle?.color != null || + _themeData.dataLabelTextStyle?.color != null; + } + @override bool get isRepaintBoundary => true; @override void performLayout() { - size = _getBoxSize(constraints); + size = getBoxSize(constraints); } @override void attach(PipelineOwner owner) { super.attach(owner); - _state.dataLabelAnimationController.addListener(markNeedsPaint); - if (defaultController != null) { - defaultController.addZoomingListener(_handleZooming); - defaultController.addPanningListener(_handlePanning); - defaultController.addResetListener(_handleReset); + dataLabelAnimationController.addListener(markNeedsPaint); + if (controller == null) { + final RenderShapeLayer shapeLayerRenderBox = parent; + controller = shapeLayerRenderBox.controller; + } + + if (controller != null) { + controller + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addRefreshListener(_handleRefresh) + ..addResetListener(_handleReset); } } @override void detach() { - _state.dataLabelAnimationController.removeListener(markNeedsPaint); - if (defaultController != null) { - defaultController.removeZoomingListener(_handleZooming); - defaultController.removePanningListener(_handlePanning); - defaultController.removeResetListener(_handleReset); + dataLabelAnimationController.removeListener(markNeedsPaint); + if (controller != null) { + controller + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeRefreshListener(_handleRefresh) + ..removeResetListener(_handleReset); } + super.detach(); } @@ -160,12 +200,11 @@ class _RenderMapDataLabel extends _ShapeLayerChildRenderBoxBase { markNeedsPaint(); } - void _handleReset() { + void _handleRefresh() { markNeedsPaint(); } - @override - void refresh() { + void _handleReset() { markNeedsPaint(); } @@ -175,16 +214,14 @@ class _RenderMapDataLabel extends _ShapeLayerChildRenderBoxBase { context.canvas ..save() ..clipRect(offset & size); - defaultController.applyTransform(context, offset); + controller.applyTransform(context, offset); String dataLabelText; final TextStyle textStyle = _effectiveTextStyle.copyWith( color: _getAnimatedColor(_effectiveTextStyle.color)); - final bool hasMapper = _state.widget.delegate.dataLabelMapper != null; - final bool isCustomTextStyle = - _settings.textStyle != null || _themeData.dataLabelTextStyle != null; - mapDataSource.forEach((String key, _MapModel model) { - dataLabelText = defaultController.isInInteractive + final bool hasMapper = source.dataLabelMapper != null; + mapDataSource.forEach((String key, MapModel model) { + dataLabelText = controller.isInInteractive ? model.visibleDataLabelText : hasMapper ? model.dataLabelText @@ -194,18 +231,26 @@ class _RenderMapDataLabel extends _ShapeLayerChildRenderBoxBase { } final TextStyle desiredTextStyle = - _updateLuminanceColor(textStyle, isCustomTextStyle, model); + _updateLuminanceColor(textStyle, _isCustomTextColor, model); _textPainter.text = TextSpan(style: desiredTextStyle, text: dataLabelText); _textPainter.layout(); - if (!defaultController.isInInteractive) { - if (_settings.overflowMode == MapLabelOverflowMode.hide) { + if (!controller.isInInteractive) { + if (_settings.overflowMode == MapLabelOverflow.hide) { if (_textPainter.width > model.shapeWidth) { model.visibleDataLabelText = null; return; } - } else if (_settings.overflowMode == MapLabelOverflowMode.trim) { - _trimText(dataLabelText, model.shapeWidth, desiredTextStyle); + } else if (_settings.overflowMode == MapLabelOverflow.ellipsis) { + final String trimmedText = getTrimText( + dataLabelText, + desiredTextStyle, + model.shapeWidth, + _textPainter, + _textPainter.width); + _textPainter.text = + TextSpan(style: desiredTextStyle, text: trimmedText); + _textPainter.layout(); } final TextSpan textSpan = _textPainter.text; @@ -214,7 +259,7 @@ class _RenderMapDataLabel extends _ShapeLayerChildRenderBoxBase { context.canvas ..save() ..translate(model.shapePathCenter.dx, model.shapePathCenter.dy) - ..scale(1 / defaultController.localScale); + ..scale(1 / controller.localScale); _textPainter.paint(context.canvas, Offset(-_textPainter.width / 2, -_textPainter.height / 2)); context.canvas.restore(); @@ -224,7 +269,7 @@ class _RenderMapDataLabel extends _ShapeLayerChildRenderBoxBase { } TextStyle _updateLuminanceColor( - TextStyle style, bool isCustomTextStyle, _MapModel model) { + TextStyle style, bool isCustomTextStyle, MapModel model) { if (!isCustomTextStyle) { final Brightness brightness = ThemeData.estimateBrightnessForColor(_getActualShapeColor(model)); @@ -236,34 +281,11 @@ class _RenderMapDataLabel extends _ShapeLayerChildRenderBoxBase { return style; } - Color _getActualShapeColor(_MapModel model) { - return model.shapeColor ?? - (_state.paletteLength > 0 - ? _state.widget.palette[model.actualIndex % _state.paletteLength] - : _themeData.layerColor); + Color _getActualShapeColor(MapModel model) { + return model.shapeColor ?? _themeData.layerColor; } Color _getAnimatedColor(Color color) { return color.withOpacity(_opacityTween.evaluate(_dataLabelAnimation)); } - - void _trimText(String dataLabelText, double shapeWidth, TextStyle textStyle) { - final int actualTextLength = dataLabelText.length; - String trimmedText = dataLabelText; - int trimLength = 3; // 3 dots - while (_textPainter.width > shapeWidth) { - if (trimmedText.length <= 4) { - _textPainter.text = - TextSpan(style: textStyle, text: trimmedText[0] + '...'); - _textPainter.layout(); - break; - } else { - trimmedText = dataLabelText.replaceRange( - actualTextLength - trimLength, actualTextLength, '...'); - _textPainter.text = TextSpan(style: textStyle, text: trimmedText); - _textPainter.layout(); - trimLength++; - } - } - } } diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart b/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart new file mode 100644 index 000000000..5057d7494 --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart @@ -0,0 +1,2398 @@ +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/theme.dart'; +import 'package:syncfusion_flutter_maps/src/utils.dart'; + +import '../controller/default_controller.dart'; +import '../enum.dart'; +import '../layer/shape_layer.dart'; +import '../settings.dart'; +import '../utils.dart'; +import 'shapes.dart'; + +enum _MapLegendType { + vector, + + bar +} + +/// Shows legend for the bubbles or shapes. +/// +/// Information provided in the legend helps to identify the data rendered in +/// the map shapes or bubbles. +/// +/// Defaults to `null`. +/// +/// By default, legend will not be shown. +/// +/// ## Legend for shape +/// +/// Set [MapLegend.source] to [MapElement.shape] to show legend for shapes. +/// +/// If [MapShapeSource.shapeColorMappers] is not null, then +/// [MapColorMapper.color] and [MapColorMapper.text] will be used for the +/// legend item's icon and the legend item's text respectively. +/// +/// If [MapShapeSource.shapeColorMappers] is null, the color returned +/// in the [MapShapeSource.shapeColorValueMapper] will be applied to +/// the legend item's icon and the legend item's text will be taken from the +/// [MapShapeSource.shapeDataField]. +/// +/// In a rare case, if both the [MapShapeSource.shapeColorMappers] and +/// the [MapShapeSource.shapeColorValueMapper] properties are null, +/// the legend item's text will be taken from the +/// [MapShapeSource.shapeDataField] property and the legend item's +/// icon will have the default color. +/// +/// ## Legend for bubbles +/// +/// Set [MapLegend.source] to [MapElement.bubble] to show legend for bubbles. +/// +/// If [MapShapeSource.bubbleColorMappers] is not null, then +/// [MapColorMapper.color] and [MapColorMapper.text] will be used for the +/// legend item's icon and the legend item's text respectively. +/// +/// If [MapShapeSource.bubbleColorMappers] is null, the color returned +/// in the [MapShapeSource.bubbleColorValueMapper] will be applied to +/// the legend item's icon and the legend item's text will be taken from the +/// [MapShapeSource.shapeDataField]. +/// +/// If both the [MapShapeSource.bubbleColorMappers] and +/// the [MapShapeSource.bubbleColorValueMapper] properties are null, +/// the legend item's text will be taken from the +/// [MapShapeSource.shapeDataField] property and the legend item's +/// icon will have the default color. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfMaps( +/// layers: [ +/// MapShapeLayer( +/// legend: MapLegend( +/// MapElement.bubble, +/// padding: EdgeInsets.all(10) +/// ), +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// dataCount: bubbleData.length, +/// primaryValueMapper: (int index) { +/// return bubbleData[index].country; +/// }), +/// ) +/// ], +/// ); +/// } +/// ``` +@immutable +class MapLegend extends DiagnosticableTree { + /// Creates a legend with different styles like circle, rectangle, triangle + /// and square for the bubbles or shapes. + /// + /// Information provided in the legend helps to identify the data rendered in + /// the map shapes or bubbles. + /// + /// Defaults to `null`. + /// + /// By default, legend will not be shown. + /// + /// ## Legend for shape + /// + /// Set [MapLegend.source] to [MapElement.shape] to show legend for shapes. + /// + /// If [MapShapeSource.shapeColorMappers] is not null, then + /// [MapColorMapper.color] and [MapColorMapper.text] will be used for the + /// legend item's icon and the legend item's text respectively. + /// + /// If [MapShapeSource.shapeColorMappers] is null, the color returned + /// in the [MapShapeSource.shapeColorValueMapper] will be applied to + /// the legend item's icon and the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField]. + /// + /// In a rare case, if both the [MapShapeSource.shapeColorMappers] and + /// the [MapShapeSource.shapeColorValueMapper] properties are null, + /// the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField] property and the legend item's + /// icon will have the default color. + /// + /// ## Legend for bubbles + /// + /// Set [MapLegend.source] to [MapElement.bubble] to show legend for bubbles. + /// + /// If [MapShapeSource.bubbleColorMappers] is not null, then + /// [MapColorMapper.color] and [MapColorMapper.text] will be used for the + /// legend item's icon and the legend item's text respectively. + /// + /// If [MapShapeSource.bubbleColorMappers] is null, the color returned + /// in the [MapShapeSource.bubbleColorValueMapper] will be applied to + /// the legend item's icon and the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField]. + /// + /// If both the [MapShapeSource.bubbleColorMappers] and + /// the [MapShapeSource.bubbleColorValueMapper] properties are null, + /// the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField] property and the legend item's + /// icon will have the default color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.bubble, + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (int index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * `MapLegend.bar()` named constructor, for bar legend type. + const MapLegend( + this.source, { + this.position = MapLegendPosition.top, + this.offset, + this.overflowMode = MapLegendOverflowMode.wrap, + this.direction, + this.padding = const EdgeInsets.all(10.0), + this.spacing = 10.0, + MapIconType iconType, + Size iconSize, + this.textStyle, + bool enableToggleInteraction = false, + Color toggledItemColor, + Color toggledItemStrokeColor, + double toggledItemStrokeWidth = 1.0, + double toggledItemOpacity = 1.0, + }) : _legendType = _MapLegendType.vector, + _iconType = iconType ?? MapIconType.circle, + _iconSize = iconSize ?? const Size(8.0, 8.0), + _enableToggleInteraction = enableToggleInteraction, + _toggledItemColor = toggledItemColor, + _toggledItemStrokeColor = toggledItemStrokeColor, + _toggledItemStrokeWidth = toggledItemStrokeWidth, + _toggledItemOpacity = toggledItemOpacity, + _segmentSize = null, + _labelsPlacement = null, + _edgeLabelsPlacement = null, + _labelOverflow = null, + _segmentPaintingStyle = null, + assert(spacing != null && spacing >= 0), + assert(padding != null), + assert(toggledItemStrokeWidth == null || toggledItemStrokeWidth >= 0), + assert(toggledItemOpacity == null || + (toggledItemOpacity >= 0 && toggledItemOpacity <= 1)); + + /// Creates a bar type legend for the bubbles or shapes. + /// + /// Information provided in the legend helps to identify the data rendered in + /// the map shapes or bubbles. + /// + /// Defaults to `null`. + /// + /// By default, legend will not be shown. + /// + /// * labelsPlacement - Places the labels either between the bar items or on + /// the items. By default, labels placement will be + /// [MapLegendLabelsPlacement.betweenItems] when setting range color mapping + /// ([MapColorMapper]) without setting [MapColorMapper.text] property. + /// In all other cases, it will be [MapLegendLabelsPlacement.onItem]. + /// + /// * edgeLabelsPlacement - Places the edge labels either inside or at + /// center of the edges. It doesn't work with + /// [MapLegendLabelsPlacement.betweenItems]. Defaults to + /// [MapLegendEdgeLabelsPlacement.inside]. + /// + /// * segmentPaintingStyle - Option for setting solid or gradient color for + /// the bar. To enable the gradient, set this as + /// [MapLegendPaintingStyle.gradient]. Defaults to + /// [MapLegendPaintingStyle.solid]. + /// + /// * labelOverflow - Option to trim or remove the legend item's text when + /// it is overflowed. Defaults to [MapLabelOverflow.hide]. + /// + /// ## Legend for shape + /// + /// Set [MapLegend.source] to [MapElement.shape] to show legend for shapes. + /// + /// If [MapShapeSource.shapeColorMappers] is not null, then + /// [MapColorMapper.color] and [MapColorMapper.text] will be used for the + /// legend item's icon and the legend item's text respectively. + /// + /// If [MapShapeSource.shapeColorMappers] is null, the color returned + /// in the [MapShapeSource.shapeColorValueMapper] will be applied to + /// the legend item's icon and the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField]. + /// + /// If both the [MapShapeSource.shapeColorMappers] and + /// the [MapShapeSource.shapeColorValueMapper] properties are null, + /// the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField] property and the legend item's + /// icon will have the default color. + /// + /// ## Legend for bubbles + /// + /// Set [MapLegend.source] to [MapElement.bubble] to show legend for bubbles. + /// + /// If [MapShapeSource.bubbleColorMappers] is not null, then + /// [MapColorMapper.color] and [MapColorMapper.text] will be used for the + /// legend item's icon and the legend item's text respectively. + /// + /// If [MapShapeSource.bubbleColorMappers] is null, the color returned + /// in the [MapShapeSource.bubbleColorValueMapper] will be applied to + /// the legend item's icon and the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField]. + /// + /// If both the [MapShapeSource.bubbleColorMappers] and + /// the [MapShapeSource.bubbleColorValueMapper] properties are null, + /// the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField] property and the legend item's + /// icon will have the default color. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend.bar( + /// MapElement.bubble, + /// padding: EdgeInsets.all(10) + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (int index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * `MapLegend()`, for legend type with different styles like circle, + /// diamond, rectangle and triangle. + const MapLegend.bar( + this.source, { + this.overflowMode = MapLegendOverflowMode.scroll, + this.padding = const EdgeInsets.all(10.0), + this.position = MapLegendPosition.top, + this.offset, + this.spacing = 2.0, + Size segmentSize, + this.textStyle, + this.direction, + MapLegendLabelsPlacement labelsPlacement, + MapLegendEdgeLabelsPlacement edgeLabelsPlacement, + MapLabelOverflow labelOverflow, + MapLegendPaintingStyle segmentPaintingStyle, + }) : _legendType = _MapLegendType.bar, + _enableToggleInteraction = false, + _toggledItemColor = null, + _toggledItemStrokeColor = null, + _toggledItemStrokeWidth = 0.0, + _toggledItemOpacity = 0.0, + _labelsPlacement = labelsPlacement, + _edgeLabelsPlacement = + edgeLabelsPlacement ?? MapLegendEdgeLabelsPlacement.inside, + _labelOverflow = labelOverflow ?? MapLabelOverflow.visible, + _segmentPaintingStyle = + segmentPaintingStyle ?? MapLegendPaintingStyle.solid, + _segmentSize = segmentSize, + _iconType = null, + _iconSize = null, + assert(spacing != null && spacing >= 0), + assert(padding != null); + + /// Shows legend for the bubbles or shapes. + /// + /// Information provided in the legend helps to identify the data rendered in + /// the map shapes or bubbles. + /// + /// By default, legend will not be shown. + /// + /// ## Legend for shape + /// + /// [MapElement.shape] shows legend for the shapes. + /// + /// If [MapShapeSource.shapeColorMappers] is not null, then + /// [MapColorMapper.color] and [MapColorMapper.text] will be used for the + /// legend item's icon and the legend item's text respectively. + /// + /// If [MapShapeSource.shapeColorMappers] is null, the color returned + /// in the [MapShapeSource.shapeColorValueMapper] will be applied to + /// the legend item's icon and the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField]. + /// + /// In a rare case, if both the [MapShapeSource.shapeColorMappers] and + /// the [MapShapeSource.shapeColorValueMapper] properties are null, + /// the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField] property and the legend item's + /// icon will have the default color. + /// + /// ## Legend for bubbles + /// + /// [MapElement.bubble] shows legend for the bubbles. + /// + /// If [MapShapeSource.bubbleColorMappers] is not null, then + /// [MapColorMapper.color] and [MapColorMapper.text] will be used for the + /// legend item's icon and the legend item's text respectively. + /// + /// If [MapShapeSource.bubbleColorMappers] is null, the color returned + /// in the [MapShapeSource.bubbleColorValueMapper] will be applied to + /// the legend item's icon and the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField]. + /// + /// In a rare case, if both the [MapShapeSource.bubbleColorMappers] and + /// the [MapShapeSource.bubbleColorValueMapper] properties are null, + /// the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField] property and the legend item's + /// icon will have the default color. + /// + /// See also: + /// * [legend], to enable the legend toggle interaction and customize + /// the appearance of the legend items. + final MapElement source; + + /// Sets the padding around the legend. + /// + /// Defaults to EdgeInsets.all(10.0). + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// padding: EdgeInsets.all(10) + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + final EdgeInsetsGeometry padding; + + /// Arranges the legend items in either horizontal or vertical direction. + /// + /// Defaults to horizontal, if the [position] is top, bottom or null. + /// Defaults to vertical, if the [position] is left or right. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// direction: Axis.horizontal + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [position], to set the position of the legend. + final Axis direction; + + /// Positions the legend in the different directions. + /// + /// Defaults to [MapLegendPosition.top]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// position: MapLegendPosition.bottom + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// See also: + /// * [offset], to place the legend in custom position. + final MapLegendPosition position; + + /// Places the legend in custom position. + /// + /// If the [offset] has been set and if the [position] is top, then the legend + /// will be placed in top but in the position additional to the + /// actual top position. Also, the legend will not take dedicated position for + /// it and will be drawn on the top of map. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// offset: Offset(0, 5) + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [position], to set the position of the legend. + final Offset offset; + + /// Specifies the space between the each legend items. + /// + /// Defaults to 10.0 for default legend and 2.0 for bar legend. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// spacing: 10 + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + final double spacing; + + /// Customizes the legend item's text style. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// textStyle: TextStyle(color: Colors.red) + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + final TextStyle textStyle; + + /// Wraps or scrolls the legend items when it overflows. + /// + /// In wrap mode, overflowed items will be wrapped in a new row and will + /// be positioned from the start. + /// + /// If the legend position is left or right, scroll direction is vertical. + /// If the legend position is top or bottom, scroll direction is horizontal. + /// + /// Defaults to [MapLegendOverflowMode.wrap] for default legend and + /// [MapLegendOverflowMode.scroll] for bar legend. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// overflowMode: MapLegendOverflowMode.scroll + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [position], to set the position of the legend. + /// * [direction], to set the direction of the legend. + final MapLegendOverflowMode overflowMode; + + /// Enables the toggle interaction for the legend. + /// + /// Defaults to false. + /// + /// When this is enabled, respective shape or bubble for the legend item will + /// be toggled on tap or click. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// enableToggleInteraction: true + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + final bool _enableToggleInteraction; + + /// Fills the toggled legend item's icon and the respective shape or bubble + /// by this color. + /// + /// This snippet shows how to set toggledItemColor in [SfMaps]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// enableToggleInteraction: true, + /// toggledItemColor: Colors.blueGrey + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [toggledItemStrokeColor], [toggledItemStrokeWidth], to set the stroke + /// for the toggled legend item's shape or bubble. + final Color _toggledItemColor; + + /// Stroke color for the toggled legend item's respective shape or bubble. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// enableToggleInteraction: true, + /// toggledItemColor: Colors.blueGrey, + /// toggledItemStrokeColor: Colors.white, + /// toggledItemStrokeWidth: 0.5 + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [toggledItemStrokeWidth], to set the stroke width for the + /// toggled legend item's shape. + final Color _toggledItemStrokeColor; + + /// Stroke width for the toggled legend item's respective shape or + /// bubble. + /// + /// Defaults to 1.0. + /// + /// This snippet shows how to set toggledItemStrokeWidth in [SfMaps]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// enableToggleInteraction: true, + /// toggledItemColor: Colors.blueGrey, + /// toggledItemStrokeColor: Colors.white, + /// toggledItemStrokeWidth: 0.5 + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [toggledItemStrokeColor], to set the stroke color for the + /// toggled legend item's shape. + final double _toggledItemStrokeWidth; + + /// Sets the toggled legend item's respective shape or + /// bubble opacity. + /// + /// Defaults to 1.0. + /// + /// This snippet shows how to set toggledItemOpacity in [SfMaps]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// enableToggleInteraction: true, + /// toggledItemColor: Colors.blueGrey, + /// toggledItemStrokeColor: Colors.white, + /// toggledItemStrokeWidth: 0.5, + /// toggledItemOpacity: 0.5 + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + final double _toggledItemOpacity; + + /// Specifies the shape of the legend icon. + /// + /// Defaults to [MapIconType.circle]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// iconType: MapIconType.rectangle + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [iconSize], to set the size of the icon. + final MapIconType _iconType; + + /// Customizes the size of the bar segments. + /// + /// Defaults to Size(80.0, 12.0). + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// segmentSize: Size(30, 10) + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + final Size _segmentSize; + + /// Customizes the size of the icon. + /// + /// Defaults to Size(12.0, 12.0). + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// iconSize: Size(30, 10) + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [iconType], to set shape of the default legend icon. + final Size _iconSize; + + /// Place the labels either between the segments or on the segments. + /// + /// By default, label placement will be[MapLegendLabelsPlacement.betweenItems] + /// when setting range color mapper without setting color mapper text property + /// otherwise label placement will be [MapLegendLabelsPlacement.onItem]. + /// + /// This snippet shows how to set label placement in [SfMaps]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend.bar( + /// MapElement.bubble, + /// labelsPlacement: MapLegendLabelsPlacement.onItem, + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [edgeLabelsPlacement], to place the edge labels either + /// inside or outside of the bar legend. + /// + /// * [labelOverflow], to trims or removes the legend text + /// when it is overflowed from the bar legend. + final MapLegendLabelsPlacement _labelsPlacement; + + /// Place the edge labels either inside or outside of the bar legend. + /// + /// [edgeLabelsPlacement] doesn't works with + /// [MapLegendLabelsPlacement.betweenItems]. + /// + /// Defaults to [MapLegendEdgeLabelsPlacement.inside]. + /// + /// This snippet shows how to set edge label placement in [SfMaps]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend.bar( + /// MapElement.bubble, + /// edgeLabelsPlacement: MapLegendEdgeLabelsPlacement.inside, + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [labelsPlacement], place the labels either between the segments or + /// on the segments. + /// * [labelOverflow], to trims or removes the legend text + /// when it is overflowed from the bar legend. + final MapLegendEdgeLabelsPlacement _edgeLabelsPlacement; + + /// Trims or removes the legend text when it is overflowed from the + /// bar legend. + /// Defaults to [MapLabelOverflow.hide]. + /// + /// By default, the legend labels will render even if it overflows form the + /// bar legend. Using this property, it is possible to remove or trim the + /// legend labels based on the bar legend size. + /// + /// This snippet shows how to set the [overflowMode] for the bar legend text + /// in [SfMaps]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// legend: MapLegend( + /// MapElement.shape, + /// labelOverflow: MapLabelOverflow.hide + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [labelsPlacement], place the labels either between the segments or + /// on the segments. + /// * [edgeLabelsPlacement], to place the edge labels either inside or + /// outside of the bar legend. + final MapLabelOverflow _labelOverflow; + + /// Specifies the type of the legend. + final _MapLegendType _legendType; + + final MapLegendPaintingStyle _segmentPaintingStyle; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + if (other is MapLegend && other._legendType != _legendType) { + return false; + } + + return other is MapLegend && + other.padding == padding && + other.offset == offset && + other.spacing == spacing && + other.direction == direction && + other.overflowMode == overflowMode && + other.position == position && + other.textStyle == textStyle && + other._enableToggleInteraction == _enableToggleInteraction && + other._toggledItemColor == _toggledItemColor && + other._toggledItemStrokeColor == _toggledItemStrokeColor && + other._toggledItemStrokeWidth == _toggledItemStrokeWidth && + other._toggledItemOpacity == _toggledItemOpacity && + other.source == source; + } + + @override + int get hashCode => hashValues( + padding, + offset, + spacing, + direction, + overflowMode, + position, + textStyle, + _enableToggleInteraction, + _toggledItemColor, + _toggledItemStrokeColor, + _toggledItemStrokeWidth, + _toggledItemOpacity, + source, + ); + + /// Creates a copy of this class but with the given fields + /// replaced with the new values. + MapLegend copyWith({ + Axis direction, + EdgeInsetsGeometry padding, + MapLegendPosition position, + Offset offset, + double spacing, + MapIconType iconType, + TextStyle textStyle, + Size iconSize, + Size segmentSize, + MapLegendOverflowMode overflowMode, + bool enableToggleInteraction, + Color toggledItemColor, + Color toggledItemStrokeColor, + double toggledItemStrokeWidth, + double toggledItemOpacity, + MapElement source, + MapLegendLabelsPlacement labelsPlacement, + MapLegendEdgeLabelsPlacement edgeLabelsPlacement, + MapLabelOverflow labelOverflow, + MapLegendPaintingStyle segmentPaintingStyle, + }) { + if (_legendType == _MapLegendType.vector) { + return MapLegend( + source ?? this.source, + direction: direction ?? this.direction, + padding: padding ?? this.padding, + position: position ?? this.position, + offset: offset ?? this.offset, + spacing: spacing ?? this.spacing, + iconType: iconType ?? _iconType, + textStyle: textStyle ?? this.textStyle, + iconSize: iconSize ?? _iconSize, + overflowMode: overflowMode ?? this.overflowMode, + enableToggleInteraction: + enableToggleInteraction ?? _enableToggleInteraction, + toggledItemColor: toggledItemColor ?? _toggledItemColor, + toggledItemStrokeColor: + toggledItemStrokeColor ?? _toggledItemStrokeColor, + toggledItemStrokeWidth: + toggledItemStrokeWidth ?? _toggledItemStrokeWidth, + toggledItemOpacity: toggledItemOpacity ?? _toggledItemOpacity, + ); + } else { + return MapLegend.bar( + source ?? this.source, + direction: direction ?? this.direction, + padding: padding ?? this.padding, + position: position ?? this.position, + offset: offset ?? this.offset, + spacing: spacing ?? this.spacing, + textStyle: textStyle ?? this.textStyle, + segmentSize: segmentSize ?? _segmentSize, + overflowMode: overflowMode ?? this.overflowMode, + labelsPlacement: labelsPlacement ?? _labelsPlacement, + edgeLabelsPlacement: edgeLabelsPlacement ?? _edgeLabelsPlacement, + labelOverflow: labelOverflow ?? _labelOverflow, + segmentPaintingStyle: segmentPaintingStyle ?? _segmentPaintingStyle, + ); + } + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + + properties.add(EnumProperty('source', source)); + properties.add(EnumProperty<_MapLegendType>('legendType', _legendType)); + properties.add(DiagnosticsProperty('padding', padding)); + properties.add(DiagnosticsProperty('offset', offset)); + properties.add(DoubleProperty('spacing', spacing)); + if (direction != null) { + properties.add(EnumProperty('direction', direction)); + } + + properties + .add(EnumProperty('overflowMode', overflowMode)); + properties.add(EnumProperty('position', position)); + if (textStyle != null) { + properties.add(textStyle.toDiagnosticsNode(name: 'textStyle')); + } + + if (_legendType == _MapLegendType.vector) { + properties.add(DiagnosticsProperty('iconSize', _iconSize)); + properties.add(EnumProperty('iconType', _iconType)); + 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)); + } + + properties.add( + 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)); + } + } +} + +/// For rendering the map legend based on legend type. +class MapLayerLegend extends StatefulWidget { + /// Creates a [MapMarker]. + MapLayerLegend({ + this.dataSource, + this.legend, + this.themeData, + this.controller, + this.toggleAnimationController, + }) : source = legend.source, + textStyle = legend.textStyle, + enableToggleInteraction = legend._enableToggleInteraction, + toggledItemColor = legend._toggledItemColor, + toggledItemStrokeColor = legend._toggledItemStrokeColor, + toggledItemStrokeWidth = legend._toggledItemStrokeWidth, + toggledItemOpacity = legend._toggledItemOpacity; + + /// map with the respective data in the data source. + final dynamic dataSource; + + /// Customizes the appearance of the the legend. + final MapLegend legend; + + /// Shows legend for the bubbles or shapes. + final MapElement source; + + /// Customizes the legend item's text style. + final TextStyle textStyle; + + /// Enables the toggle interaction for the legend. + final bool enableToggleInteraction; + + /// Fills the toggled legend item's icon and the respective shape or bubble + /// by this color. + final Color toggledItemColor; + + /// Stroke color for the toggled legend item's respective shape or bubble. + final Color toggledItemStrokeColor; + + /// Stroke width for the toggled legend item's respective shape or + /// bubble. + final double toggledItemStrokeWidth; + + /// Sets the toggled legend item's respective shape or + /// bubble opacity. + final double toggledItemOpacity; + + /// Holds the color and typography values for a [SfMapsTheme]. + /// Use this class to configure a [SfMapsTheme] widget, + /// or to set the [SfThemeData.mapsThemeData] for a [SfTheme] widget. + final SfMapsThemeData themeData; + + /// updating legend toggled indices. + final MapController controller; + + /// Applies animation for toggled legend item. + final AnimationController toggleAnimationController; + + /// Creates a copy of this class but with the given fields + /// replaced with the new values. + MapLayerLegend copyWith({ + dynamic dataSource, + MapLegend legend, + SfMapsThemeData themeData, + MapController controller, + AnimationController toggleAnimationController, + }) { + return MapLayerLegend( + dataSource: dataSource ?? this.dataSource, + legend: legend ?? this.legend, + themeData: themeData ?? this.themeData, + controller: controller ?? this.controller, + toggleAnimationController: + toggleAnimationController ?? this.toggleAnimationController, + ); + } + + @override + _MapLegendState createState() => _MapLegendState(); +} + +class _MapLegendState extends State { + @override + void didUpdateWidget(MapLayerLegend oldWidget) { + if (oldWidget.legend.source != widget.legend.source) { + _resetToggleState(); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _resetToggleState(); + super.dispose(); + } + + void _resetToggleState() { + widget.controller.currentToggledItemIndex = -1; + widget.controller.toggledIndices.clear(); + } + + @override + // ignore: missing_return + Widget build(BuildContext context) { + switch (widget.legend.overflowMode) { + case MapLegendOverflowMode.scroll: + return SingleChildScrollView( + scrollDirection: widget.legend.position == MapLegendPosition.top || + widget.legend.position == MapLegendPosition.bottom + ? Axis.horizontal + : Axis.vertical, + child: actualChild, + ); + case MapLegendOverflowMode.wrap: + return actualChild; + } + } + + Widget get actualChild { + if (widget.legend._legendType == _MapLegendType.vector) { + return Padding( + padding: widget.legend.padding, + // Mapped the legend properties to the Wrap widget. + child: Wrap( + direction: widget.legend.direction ?? + (widget.legend.position == MapLegendPosition.top || + widget.legend.position == MapLegendPosition.bottom + ? Axis.horizontal + : Axis.vertical), + spacing: widget.legend.spacing, + children: _getLegendItems(), + runSpacing: 6, + runAlignment: WrapAlignment.center, + alignment: WrapAlignment.start, + ), + ); + } else { + if (widget.legend._segmentPaintingStyle == MapLegendPaintingStyle.solid) { + return _SolidBarLegend( + dataSource: widget.dataSource, + legend: widget.legend, + themeData: widget.themeData, + ); + } else { + return _GradientBarLegend( + dataSource: widget.dataSource, + settings: widget.legend, + themeData: widget.themeData, + ); + } + } + } + + /// Returns the list of legend items based on the data source. + List _getLegendItems() { + final List legendItems = []; + final bool isLegendForBubbles = widget.legend.source == MapElement.bubble; + if (widget.dataSource != null && widget.dataSource.isNotEmpty) { + // Here source be either shape color mappers or bubble color mappers. + if (widget.dataSource is List) { + final int length = widget.dataSource.length; + for (int i = 0; i < length; i++) { + final MapColorMapper colorMapper = widget.dataSource[i]; + assert(colorMapper != null); + final String text = colorMapper.text ?? + colorMapper.value ?? + '${colorMapper.from} - ${colorMapper.to}'; + assert(text != null); + legendItems.add(_getLegendItem( + text, + colorMapper.color, + i, + isLegendForBubbles, + )); + } + } else { + // Here source is map data source. + widget.dataSource.forEach((String key, MapModel mapModel) { + assert(mapModel.primaryKey != null); + legendItems.add(_getLegendItem( + mapModel.primaryKey, + isLegendForBubbles ? mapModel.bubbleColor : mapModel.shapeColor, + mapModel.legendMapperIndex, + isLegendForBubbles, + )); + }); + } + } + + return legendItems; + } + + /// Returns the legend icon and label. + Widget _getLegendItem( + String text, Color color, int index, bool isLegendForBubbles) { + assert(text != null); + return _MapLegendItem( + index: index, + text: text, + iconShapeColor: color ?? + (isLegendForBubbles + ? widget.themeData.bubbleColor + : widget.themeData.layerColor), + source: widget.legend.source, + legend: widget.legend, + themeData: widget.themeData, + controller: widget.controller, + toggleAnimationController: widget.toggleAnimationController, + ); + } +} + +class _MapLegendItem extends LeafRenderObjectWidget { + const _MapLegendItem({ + this.index, + this.text, + this.iconShapeColor, + this.source, + this.legend, + this.themeData, + this.controller, + this.toggleAnimationController, + }); + + final int index; + final String text; + final Color iconShapeColor; + final MapElement source; + final MapLegend legend; + final SfMapsThemeData themeData; + final MapController controller; + final AnimationController toggleAnimationController; + + @override + RenderObject createRenderObject(BuildContext context) { + return _RenderLegendItem( + index: index, + text: text, + iconShapeColor: iconShapeColor, + source: source, + legend: legend, + themeData: themeData, + controller: controller, + toggleAnimationController: toggleAnimationController, + mediaQueryData: MediaQuery.of(context), + ); + } + + @override + void updateRenderObject( + BuildContext context, _RenderLegendItem renderObject) { + renderObject + ..text = text + ..iconShapeColor = iconShapeColor + ..source = source + ..legend = legend + ..themeData = themeData + ..mediaQueryData = MediaQuery.of(context); + } +} + +class _RenderLegendItem extends RenderBox implements MouseTrackerAnnotation { + _RenderLegendItem({ + int index, + String text, + Color iconShapeColor, + MapElement source, + MapLegend legend, + SfMapsThemeData themeData, + MapController controller, + AnimationController toggleAnimationController, + MediaQueryData mediaQueryData, + }) : _index = index, + _text = text, + _iconShapeColor = iconShapeColor, + _source = source, + _legend = legend, + _themeData = themeData, + controller = controller, + _toggleAnimationController = toggleAnimationController, + _mediaQueryData = mediaQueryData { + _textPainter = TextPainter(textDirection: TextDirection.ltr); + _updateTextPainter(); + _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; + _toggleColorAnimation = CurvedAnimation( + parent: _toggleAnimationController, curve: Curves.easeInOut); + _iconColorTween = ColorTween(); + _textOpacityTween = Tween(); + _updateToggledIconColor(); + } + + final int _spacing = 3; + + final int _index; + + final double _toggledTextOpacity = 0.5; + + final double _untoggledTextOpacity = 1.0; + + final MapIconShape _iconShape = const MapIconShape(); + + final AnimationController _toggleAnimationController; + + MapController controller; + + bool _wasToggled = false; + + TextPainter _textPainter; + + TapGestureRecognizer _tapGestureRecognizer; + + Animation _toggleColorAnimation; + + Tween _textOpacityTween; + + ColorTween _iconColorTween; + + Color _toggledIconColor; + + String get text => _text; + String _text; + set text(String value) { + if (_text == value) { + return; + } + _text = value; + _updateTextPainter(); + markNeedsLayout(); + } + + Color get iconShapeColor => _iconShapeColor; + Color _iconShapeColor; + set iconShapeColor(Color value) { + if (_iconShapeColor == value) { + return; + } + _iconShapeColor = value; + markNeedsPaint(); + } + + MapElement get source => _source; + MapElement _source; + set source(MapElement value) { + if (_source == value) { + return; + } + _source = value; + _wasToggled = false; + markNeedsPaint(); + } + + MapLegend get legend => _legend; + MapLegend _legend; + set legend(MapLegend value) { + if (_legend == value) { + return; + } + _legend = value; + _updateTextPainter(); + markNeedsLayout(); + } + + SfMapsThemeData get themeData => _themeData; + SfMapsThemeData _themeData; + set themeData(SfMapsThemeData value) { + if (_themeData == value) { + return; + } + _themeData = value; + _updateToggledIconColor(); + markNeedsPaint(); + } + + MediaQueryData get mediaQueryData => _mediaQueryData; + MediaQueryData _mediaQueryData; + set mediaQueryData(MediaQueryData value) { + if (_mediaQueryData == value) { + return; + } + _mediaQueryData = value; + _updateTextPainter(); + markNeedsLayout(); + } + + @override + MouseCursor get cursor => _legend._enableToggleInteraction + ? SystemMouseCursors.click + : SystemMouseCursors.basic; + + @override + PointerEnterEventListener get onEnter => null; + + // As onHover property of MouseHoverAnnotation was removed only in the + // beta channel, once it is moved to stable, will remove this property. + @override + // ignore: override_on_non_overriding_member + PointerHoverEventListener get onHover => null; + + @override + PointerExitEventListener get onExit => null; + + @override + // ignore: override_on_non_overriding_member + bool get validForMouseTracker => true; + + void _handleTapUp(TapUpDetails details) { + _wasToggled = !controller.toggledIndices.contains(_index); + if (_wasToggled) { + controller.toggledIndices.add(_index); + _iconColorTween.begin = _iconShapeColor; + _iconColorTween.end = _toggledIconColor; + _textOpacityTween.begin = _untoggledTextOpacity; + _textOpacityTween.end = _toggledTextOpacity; + } else { + controller.toggledIndices.remove(_index); + _iconColorTween.begin = _toggledIconColor; + _iconColorTween.end = _iconShapeColor; + _textOpacityTween.begin = _toggledTextOpacity; + _textOpacityTween.end = _untoggledTextOpacity; + } + + controller.currentToggledItemIndex = _index; + _toggleAnimationController.forward(from: 0); + } + + void _updateTextPainter() { + _textPainter.textScaleFactor = _mediaQueryData.textScaleFactor; + _textPainter.text = TextSpan(text: _text, style: legend.textStyle); + _textPainter.layout(); + } + + void _updateToggledIconColor() { + _toggledIconColor = _themeData.toggledItemColor != Colors.transparent + ? _themeData.toggledItemColor.withOpacity(_legend._toggledItemOpacity) + : (_themeData.brightness != Brightness.dark + ? Color.fromRGBO(230, 230, 230, 1.0) + : Color.fromRGBO(66, 66, 66, 1.0)); + } + + @override + bool get isRepaintBoundary => true; + + @override + bool hitTestSelf(Offset position) => _legend._enableToggleInteraction; + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + if (_legend._enableToggleInteraction && + event.down && + event is PointerDownEvent) { + _tapGestureRecognizer.addPointer(event); + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _toggleAnimationController?.addListener(markNeedsPaint); + } + + @override + void detach() { + _toggleAnimationController?.removeListener(markNeedsPaint); + super.detach(); + } + + @override + void performLayout() { + final double width = + _legend._iconSize.width + _spacing + _textPainter.width; + final double height = + max(_legend._iconSize.height, _textPainter.height) + _spacing; + size = Size(width, height); + } + + @override + void paint(PaintingContext context, Offset offset) { + double toggledLegendItemOpacity; + Color iconColor; + Offset actualOffset; + if (_wasToggled || controller.currentToggledItemIndex == _index) { + if (controller.currentToggledItemIndex == _index) { + iconColor = _iconColorTween.evaluate(_toggleColorAnimation); + toggledLegendItemOpacity = + _textOpacityTween.evaluate(_toggleColorAnimation); + } else { + iconColor = _toggledIconColor; + toggledLegendItemOpacity = _toggledTextOpacity; + } + } else { + iconColor = _iconShapeColor; + toggledLegendItemOpacity = _untoggledTextOpacity; + } + + final Size halfIconSize = + _iconShape.getPreferredSize(_legend._iconSize, _themeData) / 2; + actualOffset = + offset + Offset(0, (size.height - (halfIconSize.height * 2)) / 2); + _iconShape.paint(context, actualOffset, + parentBox: this, + iconSize: _legend._iconSize, + color: iconColor ?? Colors.transparent, + iconType: _legend._iconType); + + _textPainter.text = TextSpan( + style: _legend.textStyle.copyWith( + color: + _legend.textStyle.color.withOpacity(toggledLegendItemOpacity)), + text: _text); + _textPainter.layout(); + actualOffset = offset + + Offset(_legend._iconSize.width + _spacing, + (size.height - _textPainter.height) / 2); + _textPainter.paint(context.canvas, actualOffset); + } +} + +class _SolidBarLegend extends StatefulWidget { + const _SolidBarLegend({ + this.dataSource, + this.legend, + this.themeData, + }); + + final dynamic dataSource; + final MapLegend legend; + final SfMapsThemeData themeData; + + @override + _SolidBarLegendState createState() => _SolidBarLegendState(); +} + +class _SolidBarLegendState extends State<_SolidBarLegend> { + Axis _direction; + TextDirection _textDirection; + MapLegendLabelsPlacement _labelsPlacement; + TextPainter _textPainter; + bool _isOverlapSegmentText = false; + Size _segmentSize; + + @override + void initState() { + _textPainter = TextPainter(textDirection: TextDirection.ltr); + super.initState(); + } + + @override + Widget build(BuildContext context) { + _segmentSize = widget.legend._segmentSize ?? const Size(80.0, 12.0); + _labelsPlacement = widget.legend._labelsPlacement; + final TextDirection textDirection = Directionality.of(context); + _direction = widget.legend.direction ?? + (widget.legend.position == MapLegendPosition.top || + widget.legend.position == MapLegendPosition.bottom + ? Axis.horizontal + : Axis.vertical); + _textDirection = textDirection == TextDirection.ltr + ? textDirection + : (_direction == Axis.vertical ? TextDirection.ltr : textDirection); + _textPainter.textScaleFactor = MediaQuery.of(context).textScaleFactor; + return Padding( + padding: widget.legend.padding, + child: Directionality( + textDirection: _textDirection, + child: Wrap( + direction: _direction, + spacing: widget.legend.spacing, + children: _getBarSegments(), + runSpacing: 6, + runAlignment: WrapAlignment.center, + alignment: WrapAlignment.start, + ), + ), + ); + } + + List _getBarSegments() { + final bool hasBubbleSource = widget.legend.source == MapElement.bubble; + if (widget.dataSource != null && widget.dataSource.isNotEmpty) { + if (widget.dataSource is List) { + return _getSegmentsForColorMapper(hasBubbleSource); + } else { + _labelsPlacement = + widget.legend._labelsPlacement ?? MapLegendLabelsPlacement.onItem; + return _getSegmentsForShapeSource(hasBubbleSource); + } + } + + return []; + } + + List _getSegmentsForColorMapper(bool isLegendForBubbles) { + final List legendItems = []; + final int length = widget.dataSource.length; + for (int i = 0; i < length; i++) { + _isOverlapSegmentText = false; + final MapColorMapper colorMapper = widget.dataSource[i]; + String currentText; + if (i == 0) { + final List firstSegmentLabels = + _getStartSegmentLabel(colorMapper); + currentText = (firstSegmentLabels.length > 1 + ? firstSegmentLabels[1] + : firstSegmentLabels[0]); + } else { + currentText = _getText(colorMapper); + } + + if (i < length - 1 && + _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + currentText = _getTrimmedText( + currentText, _getText(widget.dataSource[i + 1]), i, length); + } else if (_direction == Axis.horizontal && + _labelsPlacement == MapLegendLabelsPlacement.onItem) { + _isOverlapSegmentText = _getTextWidth(currentText) > _segmentSize.width; + } + + _labelsPlacement = _labelsPlacement ?? + (colorMapper.from != null + ? MapLegendLabelsPlacement.betweenItems + : MapLegendLabelsPlacement.onItem); + legendItems.add(_getSegment(currentText, colorMapper.color, i, + isLegendForBubbles, length, colorMapper)); + } + + return legendItems; + } + + List _getSegmentsForShapeSource(bool isLegendForBubbles) { + final List barSegments = []; + final int length = widget.dataSource.length; + // If we use as iterator, it will check first and second model and then + // check third and fourth model. But we can't check second and third item + // is overlapping or not. Since the iterator in second model . So we uses + // two iterator. If we use move next first iterator gives current model and + // second iterator gives next model. + final Iterator currentIterator = + widget.dataSource.values.iterator; + final Iterator nextIterator = widget.dataSource.values.iterator; + nextIterator.moveNext(); + while (currentIterator.moveNext()) { + final MapModel mapModel = currentIterator.current; + String text = mapModel.primaryKey; + if (nextIterator.moveNext() && + _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + text = _getTrimmedText(text, nextIterator.current.primaryKey, + mapModel.actualIndex, length); + } else if (_direction == Axis.horizontal && + _labelsPlacement == MapLegendLabelsPlacement.onItem) { + _isOverlapSegmentText = _getTextWidth(text) > _segmentSize.width; + } + + barSegments.add(_getSegment( + text, + isLegendForBubbles ? mapModel.bubbleColor : mapModel.shapeColor, + mapModel.legendMapperIndex, + isLegendForBubbles, + length)); + } + + return barSegments; + } + + String _getTrimmedText( + String currentText, String nextText, int index, int length) { + if (widget.legend._labelOverflow == MapLabelOverflow.visible) { + return currentText; + } + + final Size barSize = _segmentSize; + if (_direction == Axis.horizontal && + _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + final double refCurrentTextWidth = _getTextWidth(currentText) / 2; + final double refNextTextWidth = index + 1 == length - 1 && + widget.legend._edgeLabelsPlacement == + MapLegendEdgeLabelsPlacement.inside + ? _getTextWidth(nextText) + : _getTextWidth(nextText) / 2; + _isOverlapSegmentText = refCurrentTextWidth + refNextTextWidth > + barSize.width + widget.legend.spacing; + if (widget.legend._labelOverflow == MapLabelOverflow.ellipsis) { + if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + final double textWidth = refCurrentTextWidth + refNextTextWidth; + return getTrimText( + currentText, + widget.legend.textStyle, + _segmentSize.width + widget.legend.spacing / 2, + _textPainter, + textWidth, + refNextTextWidth); + } + } + } + + return currentText; + } + + String _getText(MapColorMapper colorMapper) { + return colorMapper.text ?? + colorMapper.value ?? + (_labelsPlacement == MapLegendLabelsPlacement.betweenItems + ? colorMapper.to.toString() + : (_textDirection == TextDirection.ltr + ? '${colorMapper.from} - ${colorMapper.to}' + : '${colorMapper.to} - ${colorMapper.from}')); + } + + double _getTextWidth(String text) { + _textPainter.text = TextSpan(text: text, style: widget.legend.textStyle); + _textPainter.layout(); + return _textPainter.width; + } + + /// Returns the bar legend icon and label. + Widget _getSegment( + String text, Color color, int index, bool isLegendForBubbles, int length, + [MapColorMapper colorMapper]) { + final Color iconColor = color ?? + (isLegendForBubbles + ? widget.themeData.bubbleColor + : widget.themeData.layerColor); + return _getBarWithLabel(iconColor, index, text, colorMapper, length); + } + + Widget _getBarWithLabel(Color iconColor, int index, String text, + MapColorMapper colorMapper, int dataSourceLength) { + Offset textOffset = _getTextOffset(index, text, dataSourceLength); + final CrossAxisAlignment crossAxisAlignment = + _getCrossAxisAlignment(index, dataSourceLength); + if (_direction == Axis.horizontal) { + textOffset = _textDirection == TextDirection.rtl && textOffset != null + ? -textOffset + : textOffset; + return Container( + width: _segmentSize.width, + child: Column( + crossAxisAlignment: crossAxisAlignment, + children: [ + Padding( + // Gap between segment text and icon. + padding: EdgeInsets.only(bottom: 10.0), + child: Container( + height: _segmentSize.height, + color: iconColor, + ), + ), + _getTextWidget(index, text, colorMapper, textOffset), + ], + ), + ); + } else { + return _getVerticalBar( + crossAxisAlignment, iconColor, index, text, colorMapper, textOffset); + } + } + + Widget _getVerticalBar(CrossAxisAlignment crossAxisAlignment, Color iconColor, + int index, String text, MapColorMapper colorMapper, Offset textOffset) { + return Container( + height: _segmentSize.width, + child: Row( + crossAxisAlignment: crossAxisAlignment, + children: [ + Padding( + // Gap between segment text and icon. + padding: EdgeInsets.only(right: 10.0), + child: Container( + width: _segmentSize.height, + color: iconColor, + ), + ), + _getTextWidget(index, text, colorMapper, textOffset), + ], + ), + ); + } + + CrossAxisAlignment _getCrossAxisAlignment(int index, int length) { + if (_labelsPlacement == MapLegendLabelsPlacement.onItem && + widget.legend._labelOverflow != MapLabelOverflow.visible) { + return CrossAxisAlignment.center; + } else { + return CrossAxisAlignment.start; + } + } + + Widget _getTextWidget( + int index, String text, MapColorMapper colorMapper, Offset legendOffset) { + if (index == 0 && + colorMapper != null && + colorMapper.from != null && + _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + return _getStartSegmentText(colorMapper, text, legendOffset); + } else { + return _getAlignedTextWidget(legendOffset, text, _isOverlapSegmentText); + } + } + + Widget _getStartSegmentText( + MapColorMapper colorMapper, String text, Offset legendOffset) { + bool isStartTextOverlapping = false; + String startText; + final List firstSegmentLabels = _getStartSegmentLabel(colorMapper); + if (firstSegmentLabels.length > 1) { + startText = firstSegmentLabels[0]; + } else { + startText = colorMapper.from?.toString(); + } + + if (_direction == Axis.horizontal && + widget.legend._labelOverflow != MapLabelOverflow.visible && + startText != null) { + final double refStartTextWidth = widget.legend._edgeLabelsPlacement == + MapLegendEdgeLabelsPlacement.inside + ? _getTextWidth(startText) + : _getTextWidth(startText) / 2; + final double refCurrentTextWidth = _getTextWidth(text) / 2; + isStartTextOverlapping = refStartTextWidth + refCurrentTextWidth > + _segmentSize.width + widget.legend.spacing; + if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + startText = getTrimText( + startText, + widget.legend.textStyle, + _segmentSize.width + widget.legend.spacing / 2, + _textPainter, + refStartTextWidth + refCurrentTextWidth, + refCurrentTextWidth); + } + } + + Offset startTextOffset = _getStartTextOffset(startText); + startTextOffset = + _textDirection == TextDirection.rtl && _direction == Axis.horizontal + ? -startTextOffset + : startTextOffset; + return Stack( + children: [ + _getAlignedTextWidget( + startTextOffset, startText, isStartTextOverlapping), + _getAlignedTextWidget(legendOffset, text, _isOverlapSegmentText), + ], + ); + } + + List _getStartSegmentLabel(MapColorMapper colorMapper) { + if (colorMapper.from != null && + colorMapper.text != null && + colorMapper.text[0] == '{' && + _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + final List splitText = colorMapper.text.split('},{'); + if (splitText.length > 1) { + splitText[0] = splitText[0].replaceAll('{', ''); + splitText[1] = splitText[1].replaceAll('}', ''); + } + + return splitText; + } else { + return [_getText(colorMapper)]; + } + } + + Widget _getAlignedTextWidget(Offset offset, String text, bool isOverlapping) { + if (widget.legend._labelOverflow == MapLabelOverflow.hide && + isOverlapping) { + return SizedBox(width: 0.0, height: 0.0); + } + return Directionality( + textDirection: TextDirection.ltr, + child: offset != Offset.zero + ? Transform.translate( + offset: offset, + child: Text( + text, + softWrap: false, + overflow: TextOverflow.visible, + style: widget.legend.textStyle, + ), + ) + : Text( + text, + textAlign: TextAlign.center, + softWrap: false, + overflow: TextOverflow.ellipsis, + style: widget.legend.textStyle, + ), + ); + } + + Offset _getTextOffset(int index, String text, int dataSourceLength) { + if (_labelsPlacement == MapLegendLabelsPlacement.onItem && + widget.legend._labelOverflow != MapLabelOverflow.visible) { + return Offset.zero; + } + + if (_direction == Axis.horizontal) { + return _getHorizontalTextOffset(index, text, dataSourceLength); + } else { + return _getVerticalTextOffset(index, text, dataSourceLength); + } + } + + Offset _getVerticalTextOffset(int index, String text, int dataSourceLength) { + _textPainter.text = TextSpan(text: text, style: widget.legend.textStyle); + _textPainter.layout(); + if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + if (index == dataSourceLength - 1) { + if (widget.legend._edgeLabelsPlacement == + MapLegendEdgeLabelsPlacement.inside) { + return Offset(0.0, _segmentSize.width - _textPainter.height); + } + return Offset(0.0, _segmentSize.width - _textPainter.height / 2); + } + + return Offset( + 0.0, + _segmentSize.width - + _textPainter.height / 2 + + widget.legend.spacing / 2); + } else { + return Offset(0.0, _segmentSize.width / 2 - _textPainter.height / 2); + } + } + + Offset _getHorizontalTextOffset( + int index, String text, int dataSourceLength) { + _textPainter.text = TextSpan(text: text, style: widget.legend.textStyle); + _textPainter.layout(); + if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + final double width = _textDirection == TextDirection.rtl && + _segmentSize.width < _textPainter.width + ? _textPainter.width + : _segmentSize.width; + if (index == dataSourceLength - 1) { + if (widget.legend._edgeLabelsPlacement == + MapLegendEdgeLabelsPlacement.inside) { + return Offset(width - _textPainter.width, 0.0); + } + return Offset(width - _textPainter.width / 2, 0.0); + } + + return Offset( + width - _textPainter.width / 2 + widget.legend.spacing / 2, 0.0); + } else { + final double xPosition = _textDirection == TextDirection.rtl && + _segmentSize.width < _textPainter.width + ? _textPainter.width / 2 - _segmentSize.width / 2 + : _segmentSize.width / 2 - _textPainter.width / 2; + return Offset(xPosition, 0.0); + } + } + + Offset _getStartTextOffset(String text) { + _textPainter.text = TextSpan(text: text, style: widget.legend.textStyle); + _textPainter.layout(); + if (widget.legend._edgeLabelsPlacement == + MapLegendEdgeLabelsPlacement.inside) { + return Offset(0.0, 0.0); + } + + if (_direction == Axis.horizontal) { + return Offset(-_textPainter.width / 2, 0.0); + } else { + return Offset(0.0, -_textPainter.height / 2); + } + } +} + +class _MapLabel { + _MapLabel(this.label, this.offset); + + String label; + + Offset offset; +} + +// ignore: must_be_immutable +class _GradientBarLegend extends StatelessWidget { + _GradientBarLegend({ + this.dataSource, + this.source, + this.settings, + this.themeData, + }); + + final dynamic dataSource; + final MapElement source; + final MapLegend settings; + final SfMapsThemeData themeData; + final List colors = []; + final List<_MapLabel> labels = <_MapLabel>[]; + + Axis _direction; + Size _segmentSize; + bool _isRTL = false; + double _referenceArea; + TextPainter _textPainter; + bool _isRangeColorMapper = false; + MapLegendLabelsPlacement _labelsPlacement; + + @override + Widget build(BuildContext context) { + _labelsPlacement = + settings._labelsPlacement ?? MapLegendLabelsPlacement.betweenItems; + _isRTL = Directionality.of(context) == TextDirection.rtl; + _textPainter = TextPainter( + textDirection: TextDirection.ltr, + textScaleFactor: MediaQuery.of(context).textScaleFactor); + _direction = settings.direction ?? + (settings.position == MapLegendPosition.top || + settings.position == MapLegendPosition.bottom + ? Axis.horizontal + : Axis.vertical); + return Padding( + padding: settings.padding, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + _updateSegmentSize(getBoxSize(constraints).shortestSide); + _collectLabelsAndColors(); + return _buildGradientBar(); + }), + ); + } + + void _updateSegmentSize(double shortestSide) { + if (_direction == Axis.horizontal) { + final double availableWidth = shortestSide - settings.padding.horizontal; + _segmentSize = settings._segmentSize == null + ? Size(availableWidth, 12.0) + : Size( + settings._segmentSize.width > availableWidth + ? availableWidth + : settings._segmentSize.width, + settings._segmentSize.height); + return; + } + + final double availableHeight = shortestSide - settings.padding.vertical; + _segmentSize = settings._segmentSize == null + ? Size(12.0, availableHeight) + : Size( + settings._segmentSize.width, + settings._segmentSize.height > availableHeight + ? availableHeight + : settings._segmentSize.height); + } + + void _collectLabelsAndColors() { + final int length = dataSource.length; + _referenceArea = _direction == Axis.horizontal + ? _segmentSize.width + : _segmentSize.height; + final double slab = _referenceArea / (length - 1); + final bool isLegendForBubbles = settings.source == MapElement.bubble; + if (dataSource != null && dataSource.isNotEmpty) { + if (dataSource is List) { + _collectColorMapperLabelsAndColors(length); + } else { + int index = 0; + dataSource.forEach((String key, MapModel mapModel) { + assert(mapModel.primaryKey != null); + labels.add(_MapLabel(mapModel.primaryKey, + _getTextOffset(mapModel.primaryKey, index, length - 1, slab))); + colors.add( + isLegendForBubbles ? mapModel.bubbleColor : mapModel.shapeColor); + index++; + }); + } + } + } + + void _collectColorMapperLabelsAndColors(int length) { + if (dataSource.isNotEmpty) { + _isRangeColorMapper = dataSource[0].from != null; + final double slab = + _referenceArea / (_isRangeColorMapper ? length : length - 1); + for (int i = 0; i < length; i++) { + final MapColorMapper colorMapper = dataSource[i]; + String text; + if (i == 0) { + final List firstSegmentLabels = + _getStartSegmentLabel(colorMapper); + text = (firstSegmentLabels.length > 1 + ? firstSegmentLabels[1] + : firstSegmentLabels[0]); + } else { + text = _getActualText(colorMapper); + } + + if (_isRangeColorMapper) { + if (i == 0 && + _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + String startText; + final List firstSegmentLabels = + _getStartSegmentLabel(colorMapper); + if (firstSegmentLabels.length > 1) { + startText = firstSegmentLabels[0]; + } else { + startText = colorMapper.from?.toString(); + } + + labels.add(_MapLabel( + startText, _getTextOffset(startText, 0, length, slab))); + } + // For range color mapper, slab is equals to the color mapper + // length. So adding +1 to point out its position index. + labels + .add(_MapLabel(text, _getTextOffset(text, i + 1, length, slab))); + } else { + // For equal color mapper, slab is equals to the color mapper + // length -1. + labels + .add(_MapLabel(text, _getTextOffset(text, i, length - 1, slab))); + } + colors.add(colorMapper.color); + } + } + } + + List _getStartSegmentLabel(MapColorMapper colorMapper) { + if (colorMapper.from != null && + colorMapper.text != null && + colorMapper.text[0] == '{' && + _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + final List splitText = colorMapper.text.split('},{'); + if (splitText.length > 1) { + splitText[0] = splitText[0].replaceAll('{', ''); + splitText[1] = splitText[1].replaceAll('}', ''); + } + + return splitText; + } else { + return [_getActualText(colorMapper)]; + } + } + + Offset _getTextOffset( + String text, int positionIndex, int length, double slab) { + _textPainter.text = TextSpan(text: text, style: settings.textStyle); + _textPainter.layout(); + final bool canAdjustLabelToCenter = + settings._edgeLabelsPlacement == MapLegendEdgeLabelsPlacement.center && + (positionIndex == 0 || positionIndex == length) || + (positionIndex > 0 && positionIndex < length); + if (_direction == Axis.horizontal) { + return _getHorizontalOffset( + canAdjustLabelToCenter, positionIndex, slab, length); + } else { + final double referenceTextWidth = canAdjustLabelToCenter + ? _textPainter.height / 2 + : (positionIndex == length ? _textPainter.height : 0.0); + return Offset(0.0, slab * positionIndex - referenceTextWidth); + } + } + + Offset _getHorizontalOffset( + bool canAdjustLabelToCenter, int positionIndex, double slab, int length) { + if (_isRTL) { + final double referenceTextWidth = canAdjustLabelToCenter + ? -_textPainter.width / 2 + : (positionIndex == 0 ? _textPainter.width : 0.0); + double dx = slab * positionIndex - referenceTextWidth; + if (positionIndex == 0) { + dx = _segmentSize.width + dx; + } else { + dx = _segmentSize.width - dx; + } + + if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + return Offset(dx, 0.0); + } + + return Offset(dx + slab / 2, 0.0); + } + + final double referenceTextWidth = canAdjustLabelToCenter + ? _textPainter.width / 2 + : (positionIndex == length ? _textPainter.width : 0.0); + if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + return Offset(slab * positionIndex - referenceTextWidth, 0.0); + } + + return Offset(slab * positionIndex - referenceTextWidth - slab / 2, 0.0); + } + + Widget _buildGradientBar() { + return _direction == Axis.horizontal + ? Column(children: _getChildren()) + : Row(children: _getChildren()); + } + + List _getChildren() { + double labelBoxWidth = _segmentSize.width; + double labelBoxHeight; + double horizontalSpacing = 0.0; + double verticalSpacing = settings.spacing; + Alignment startAlignment = Alignment.centerLeft; + Alignment endAlignment = Alignment.centerRight; + + if (_direction == Axis.vertical) { + labelBoxWidth = null; + labelBoxHeight = _segmentSize.height; + startAlignment = Alignment.topCenter; + endAlignment = Alignment.bottomCenter; + horizontalSpacing = settings.spacing; + verticalSpacing = 0.0; + } + + if (_isRTL && _direction == Axis.horizontal) { + final Alignment temp = startAlignment; + startAlignment = endAlignment; + endAlignment = temp; + } + + return [ + Container( + width: _segmentSize.width, + height: _segmentSize.height, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: startAlignment, end: endAlignment, colors: colors), + ), + ), + SizedBox(width: horizontalSpacing, height: verticalSpacing), + Container( + width: labelBoxWidth, height: labelBoxHeight, child: _getLabels()), + ]; + } + + Widget _getLabels() { + return Stack( + textDirection: TextDirection.ltr, + children: List.generate( + labels.length, + (int index) => Transform.translate( + offset: labels[index].offset, + child: Text( + labels[index].label, + style: settings.textStyle, + softWrap: false, + ), + ), + ), + ); + } + + String _getActualText(MapColorMapper colorMapper) { + return colorMapper.text ?? + colorMapper.value ?? + (_labelsPlacement == MapLegendLabelsPlacement.betweenItems + ? colorMapper.to.toString() + : (_isRTL + ? '${colorMapper.to} - ${colorMapper.from}' + : '${colorMapper.from} - ${colorMapper.to}')); + } +} diff --git a/packages/syncfusion_flutter_maps/lib/src/features/maps_marker.dart b/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart similarity index 71% rename from packages/syncfusion_flutter_maps/lib/src/features/maps_marker.dart rename to packages/syncfusion_flutter_maps/lib/src/elements/marker.dart index 8622737ac..4f25b3bf6 100644 --- a/packages/syncfusion_flutter_maps/lib/src/features/maps_marker.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart @@ -1,45 +1,85 @@ -part of maps; +import 'dart:ui'; -class _MarkerContainer extends Stack { - _MarkerContainer({ - Key key, +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/theme.dart'; + +import '../behavior/zoom_pan_behavior.dart'; +import '../controller/default_controller.dart'; +import '../enum.dart'; +import '../layer/layer_base.dart'; +import '../layer/shape_layer.dart'; +import '../utils.dart'; +import 'shapes.dart'; + +// ignore_for_file: public_member_api_docs +class ShapeLayerMarkerContainer extends Stack { + ShapeLayerMarkerContainer({ + this.tooltipKey, + this.markerTooltipBuilder, List children, - this.defaultController, - this.state, - }) : super(key: key, children: children ?? []); + this.controller, + this.sublayer, + }) : super(children: children ?? []); - final _DefaultController defaultController; - final _MapsShapeLayerState state; + final GlobalKey tooltipKey; + final IndexedWidgetBuilder markerTooltipBuilder; + final MapController controller; + final MapShapeSublayer sublayer; @override RenderStack createRenderObject(BuildContext context) { return _RenderMarkerContainer() - ..defaultController = defaultController - ..state = state; + ..tooltipKey = tooltipKey + ..markerTooltipBuilder = markerTooltipBuilder + ..controller = controller + ..sublayer = sublayer + ..markerContainer = this; + } + + @override + void updateRenderObject( + BuildContext context, _RenderMarkerContainer renderObject) { + renderObject + ..markerTooltipBuilder = markerTooltipBuilder + ..sublayer = sublayer + ..markerContainer = this; } } class _RenderMarkerContainer extends RenderStack { - _DefaultController defaultController; + GlobalKey tooltipKey; + + IndexedWidgetBuilder markerTooltipBuilder; - _MapsShapeLayerState state; + MapController controller; - double get shapeLayerSizeFactor => - defaultController.shapeLayerSizeFactor * - (defaultController.gesture == _Gesture.scale - ? defaultController.localScale - : 1); + MapShapeSublayer sublayer; + + ShapeLayerMarkerContainer markerContainer; + + int getMarkerIndex(MapMarker marker) { + return markerContainer.children.indexOf(marker); + } + + Size get markerContainerSize => + controller.isTileLayerChild ? controller.getTileSize() : size; + + double get shapeLayerSizeFactor => controller.isTileLayerChild + ? controller.shapeLayerSizeFactor + : (controller.shapeLayerSizeFactor * + ((controller.gesture == Gesture.scale) ? controller.localScale : 1)); Offset get shapeLayerOffset { - if (!defaultController.isInInteractive) { - return defaultController.shapeLayerOffset; + if (!controller.isInInteractive) { + return controller.shapeLayerOffset; } else { - if (defaultController.gesture == _Gesture.scale) { - return defaultController.getZoomingTranslation() + - defaultController.adjustment; + if (controller.gesture == Gesture.scale) { + return controller.getZoomingTranslation() + controller.normalize; } else { - return defaultController.shapeLayerOffset + - defaultController.panDistance; + return controller.shapeLayerOffset + controller.panDistance; } } } @@ -59,15 +99,20 @@ class _RenderMarkerContainer extends RenderStack { markNeedsPaint(); } + void _handleRefresh() { + _updateChildren(); + markNeedsPaint(); + } + void _updateChildren() { final double factor = shapeLayerSizeFactor; final Offset translation = shapeLayerOffset; RenderBox child = firstChild; while (child != null) { - final _RenderMapMarker marker = child; + final RenderMapMarker marker = child; final StackParentData childParentData = child.parentData; - childParentData.offset = _pixelFromLatLng( - marker.latitude, marker.longitude, size, translation, factor) - + childParentData.offset = pixelFromLatLng(marker.latitude, + marker.longitude, markerContainerSize, translation, factor) - Offset(marker.size.width / 2, marker.size.height / 2); child = childParentData.nextSibling; } @@ -76,19 +121,28 @@ class _RenderMarkerContainer extends RenderStack { @override void attach(PipelineOwner owner) { super.attach(owner); - if (defaultController != null) { - defaultController.addZoomingListener(_handleZooming); - defaultController.addPanningListener(_handlePanning); - defaultController.addResetListener(_handleReset); + if (controller == null) { + final RenderShapeLayer subLayerParent = parent.parent; + controller = subLayerParent.controller; + } + + if (controller != null) { + controller + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addResetListener(_handleReset) + ..addRefreshListener(_handleRefresh); } } @override void detach() { - if (defaultController != null) { - defaultController.removeZoomingListener(_handleZooming); - defaultController.removePanningListener(_handlePanning); - defaultController.removeResetListener(_handleReset); + if (controller != null) { + controller + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeResetListener(_handleReset) + ..removeRefreshListener(_handleRefresh); } super.detach(); } @@ -98,16 +152,16 @@ class _RenderMarkerContainer extends RenderStack { @override void performLayout() { - size = _getBoxSize(constraints); + size = getBoxSize(constraints); final double factor = shapeLayerSizeFactor; final Offset translation = shapeLayerOffset; RenderBox child = firstChild; while (child != null) { - final _RenderMapMarker marker = child; + final RenderMapMarker marker = child; final StackParentData childParentData = child.parentData; child.layout(constraints, parentUsesSize: true); - childParentData.offset = _pixelFromLatLng( - marker.latitude, marker.longitude, size, translation, factor) - + childParentData.offset = pixelFromLatLng(marker.latitude, + marker.longitude, markerContainerSize, translation, factor) - Offset(marker.size.width / 2, marker.size.height / 2); child = childParentData.nextSibling; } @@ -120,7 +174,7 @@ class _RenderMarkerContainer extends RenderStack { final StackParentData childParentData = child.parentData; final Rect childRect = Rect.fromLTWH(childParentData.offset.dx, childParentData.offset.dy, child.size.width, child.size.height); - if (defaultController.visibleBounds.overlaps(childRect)) { + if (sublayer != null || controller.visibleBounds.overlaps(childRect)) { context.paintChild(child, childParentData.offset); } child = childParentData.nextSibling; @@ -128,14 +182,14 @@ class _RenderMarkerContainer extends RenderStack { } } -/// Markers be used to denote the locations on the map. +/// Markers can be used to denote the locations on the map. /// /// It is possible to use the built-in symbols or display a custom widget at a /// specific latitude and longitude on a map. /// /// The [MapLayer.markerBuilder] callback will be called number of times equal /// to the value specified in the [MapLayer.initialMarkersCount] property. -/// The default value of the of this property is null. +/// The default value of this property is null. /// /// For rendering the custom widget for the marker, pass the required widget /// for child in [MapMarker] constructor. @@ -167,8 +221,8 @@ class _RenderMarkerContainer extends RenderStack { /// child: SfMaps( /// layers: [ /// MapShapeLayer( -/// delegate: MapShapeLayerDelegate( -/// shapeFile: 'assets/world_map.json', +/// source: MapShapeSource.asset( +/// 'assets/world_map.json', /// shapeDataField: 'name', /// dataCount: data.length, /// primaryValueMapper: (index) => data[index].country, @@ -198,7 +252,8 @@ class _RenderMarkerContainer extends RenderStack { /// } /// ``` /// See also: -/// * [MapShapeLayerController], for dynamically updating the markers. +/// * [MapShapeLayerController], [MapTileLayerController] for dynamically +/// updating the markers. class MapMarker extends SingleChildRenderObjectWidget { /// Creates a [MapMarker]. const MapMarker({ @@ -215,7 +270,7 @@ class MapMarker extends SingleChildRenderObjectWidget { assert(latitude != null), super(key: key, child: child); - /// Sets the longitude for the marker on the map. + /// Sets the latitude for the marker on the map. /// /// ```dart /// List data; @@ -244,8 +299,8 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', /// shapeDataField: 'name', /// dataCount: data.length, /// primaryValueMapper: (index) => data[index].country, @@ -275,10 +330,11 @@ class MapMarker extends SingleChildRenderObjectWidget { /// } /// ``` /// See also: - /// * [MapShapeLayerController], for dynamically updating the markers. - final double longitude; + /// * [MapShapeLayerController], [MapTileLayerController] for dynamically + /// updating the markers. + final double latitude; - /// Sets the latitude for the marker on the map. + /// Sets the longitude for the marker on the map. /// /// ```dart /// List data; @@ -307,8 +363,8 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', /// shapeDataField: 'name', /// dataCount: data.length, /// primaryValueMapper: (index) => data[index].country, @@ -338,8 +394,9 @@ class MapMarker extends SingleChildRenderObjectWidget { /// } /// ``` /// See also: - /// * [MapShapeLayerController], for dynamically updating the markers. - final double latitude; + /// * [MapShapeLayerController], [MapTileLayerController] for dynamically + /// updating the markers. + final double longitude; /// Sets the size for the marker on the map. /// @@ -375,8 +432,8 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', /// shapeDataField: 'name', /// dataCount: data.length, /// primaryValueMapper: (index) => data[index].country, @@ -407,7 +464,8 @@ class MapMarker extends SingleChildRenderObjectWidget { /// } /// ``` /// See also: - /// * [MapShapeLayerController], for dynamically updating the markers. + /// * [MapShapeLayerController], [MapTileLayerController] for dynamically + /// updating the markers. final Size size; /// Sets the icon color for the marker. @@ -439,8 +497,8 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', /// shapeDataField: 'name', /// dataCount: data.length, /// primaryValueMapper: (index) => data[index].country, @@ -471,7 +529,8 @@ class MapMarker extends SingleChildRenderObjectWidget { /// } /// ``` /// See also: - /// * [MapShapeLayerController], for dynamically updating the markers. + /// * [MapShapeLayerController], [MapTileLayerController] for dynamically + /// updating the markers. final Color iconColor; /// Sets the icon's stroke color for the marker. @@ -503,8 +562,8 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', /// shapeDataField: 'name', /// dataCount: data.length, /// primaryValueMapper: (index) => data[index].country, @@ -536,7 +595,8 @@ class MapMarker extends SingleChildRenderObjectWidget { /// } /// ``` /// See also: - /// * [MapShapeLayerController], for dynamically updating the markers. + /// * [MapShapeLayerController], [MapTileLayerController] for dynamically + /// updating the markers. final Color iconStrokeColor; /// Sets the icon's stroke width for the marker. @@ -568,8 +628,8 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', /// shapeDataField: 'name', /// dataCount: data.length, /// primaryValueMapper: (index) => data[index].country, @@ -601,7 +661,8 @@ class MapMarker extends SingleChildRenderObjectWidget { /// } /// ``` /// See also: - /// * [MapShapeLayerController], for dynamically updating the markers. + /// * [MapShapeLayerController], [MapTileLayerController] for dynamically + /// updating the markers. final double iconStrokeWidth; /// Sets the icon's shape of the marker. @@ -635,8 +696,8 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', /// shapeDataField: 'name', /// dataCount: data.length, /// primaryValueMapper: (index) => data[index].country, @@ -668,12 +729,13 @@ class MapMarker extends SingleChildRenderObjectWidget { /// } /// ``` /// See also: - /// * [MapShapeLayerController], for dynamically updating the markers. + /// * [MapShapeLayerController], [MapTileLayerController] for dynamically + /// updating the markers. final MapIconType iconType; @override RenderObject createRenderObject(BuildContext context) { - return _RenderMapMarker( + return RenderMapMarker( longitude: longitude, latitude: latitude, markerSize: size, @@ -682,11 +744,12 @@ class MapMarker extends SingleChildRenderObjectWidget { iconStrokeWidth: iconStrokeWidth, iconType: iconType, themeData: SfMapsTheme.of(context), + marker: this, ); } @override - void updateRenderObject(BuildContext context, _RenderMapMarker renderObject) { + void updateRenderObject(BuildContext context, RenderMapMarker renderObject) { renderObject ..longitude = longitude ..latitude = latitude @@ -695,12 +758,13 @@ class MapMarker extends SingleChildRenderObjectWidget { ..iconStrokeColor = iconStrokeColor ..iconStrokeWidth = iconStrokeWidth ..iconType = iconType - ..themeData = SfMapsTheme.of(context); + ..themeData = SfMapsTheme.of(context) + ..marker = this; } } -class _RenderMapMarker extends RenderProxyBox { - _RenderMapMarker({ +class RenderMapMarker extends RenderProxyBox implements MouseTrackerAnnotation { + RenderMapMarker({ double longitude, double latitude, Size markerSize, @@ -709,6 +773,7 @@ class _RenderMapMarker extends RenderProxyBox { double iconStrokeWidth, MapIconType iconType, SfMapsThemeData themeData, + MapMarker marker, }) : _longitude = longitude, _latitude = latitude, _markerSize = markerSize, @@ -716,12 +781,19 @@ class _RenderMapMarker extends RenderProxyBox { _iconStrokeColor = iconStrokeColor, _iconStrokeWidth = iconStrokeWidth, _iconType = iconType, - _themeData = themeData; + _themeData = themeData, + marker = marker { + _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; + } - final _MapIconShape _iconShape = const _MapIconShape(); + final MapIconShape _iconShape = const MapIconShape(); final Size _defaultMarkerSize = const Size(14.0, 14.0); + TapGestureRecognizer _tapGestureRecognizer; + + MapMarker marker; + double get latitude => _latitude; double _latitude; set latitude(double value) { @@ -731,7 +803,7 @@ class _RenderMapMarker extends RenderProxyBox { assert(value != null); _latitude = value; - if (super.parent is _RenderMarkerContainer) { + if (parent is _RenderMarkerContainer) { _updatePosition(); } markNeedsPaint(); @@ -746,7 +818,7 @@ class _RenderMapMarker extends RenderProxyBox { assert(value != null); _longitude = value; - if (super.parent is _RenderMarkerContainer) { + if (parent is _RenderMarkerContainer) { _updatePosition(); } markNeedsPaint(); @@ -759,7 +831,7 @@ class _RenderMapMarker extends RenderProxyBox { return; } _markerSize = value; - if (super.parent is _RenderMarkerContainer) { + if (parent is _RenderMarkerContainer) { _updatePosition(); } markNeedsLayout(); @@ -827,23 +899,99 @@ class _RenderMapMarker extends RenderProxyBox { } } + void _handleTapUp(TapUpDetails details) { + _handleInteraction(); + } + + void _handlePointerEnter(PointerEnterEvent event) { + _handleInteraction(); + } + + void _handlePointerExit(PointerExitEvent event) { + _handleInteraction(isExit: true); + } + + void _handleInteraction({bool isExit = false}) { + // For [MapMarker] shape and tile layer, we had different parent classes. + // So, we used the dynamic keyword to access both parent commonly. + final dynamic markerParent = parent; + int sublayerIndex; + if (markerParent.markerTooltipBuilder != null) { + if (markerParent is _RenderMarkerContainer && + markerParent.sublayer != null) { + final RenderShapeLayer shapeLayerRenderBox = markerParent.parent.parent; + final RenderSublayerContainer sublayerContainer = + shapeLayerRenderBox.parent; + sublayerIndex = + sublayerContainer.getSublayerIndex(markerParent.sublayer); + } + + final ShapeLayerChildRenderBoxBase tooltipRenderObject = + markerParent.controller.tooltipKey.currentContext.findRenderObject(); + final StackParentData childParentData = parentData; + // The [sublayerIndex] is not applicable, if the actual layer is + // shape or tile layer. + tooltipRenderObject.paintTooltip( + isExit ? null : markerParent.getMarkerIndex(marker), + childParentData.offset & size, + MapLayerElement.marker, + sublayerIndex); + } + } + void _updatePosition() { - final _RenderMarkerContainer parent = super.parent; + final _RenderMarkerContainer markerParent = parent; final StackParentData childParentData = parentData; if (parent != null) { - childParentData.offset = _pixelFromLatLng( + childParentData.offset = pixelFromLatLng( _latitude, _longitude, - parent.size, - parent.shapeLayerOffset, - parent.shapeLayerSizeFactor) - + markerParent.size, + markerParent.shapeLayerOffset, + markerParent.shapeLayerSizeFactor) - Offset(size.width / 2, size.height / 2); } } + @override + MouseCursor get cursor => SystemMouseCursors.basic; + + @override + PointerEnterEventListener get onEnter => _handlePointerEnter; + + // As onHover property of MouseHoverAnnotation was removed only in the + // beta channel, once it is moved to stable, will remove this property. + @override + // ignore: override_on_non_overriding_member + PointerHoverEventListener get onHover => null; + + @override + PointerExitEventListener get onExit => _handlePointerExit; + + @override + // ignore: override_on_non_overriding_member + bool get validForMouseTracker => true; + @override bool get isRepaintBoundary => true; + @override + bool hitTestSelf(Offset position) { + // For [MapMarker] shape and tile layer, we had different parent classes. + // So, we used the dynamic keyword to access both parent commonly. + final dynamic markerParent = parent; + return markerParent.markerTooltipBuilder != null; + } + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + if (event.down && event is PointerDownEvent) { + _tapGestureRecognizer.addPointer(event); + } else if (event is PointerHoverEvent) { + _handleInteraction(); + } + } + @override void performLayout() { if (_markerSize != null) { diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/shapes.dart b/packages/syncfusion_flutter_maps/lib/src/elements/shapes.dart new file mode 100644 index 000000000..96043794a --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/elements/shapes.dart @@ -0,0 +1,212 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../enum.dart'; + +// ignore_for_file: public_member_api_docs +class ShapeLayerChildRenderBoxBase extends RenderProxyBox { + void onHover(MapModel item, MapLayerElement element) {} + + void paintTooltip(int elementIndex, Rect elementRect, MapLayerElement element, + [int sublayerIndex, Offset position]) {} + + void onExit() {} + + void refresh() {} + + void hideTooltip({bool immediately = false}) {} +} + +class MapModel { + MapModel({ + this.primaryKey, + this.pixelPoints, + this.rawPoints, + this.actualIndex, + this.dataIndex, + this.legendMapperIndex, + this.shapePath, + this.isSelected = false, + this.shapeColor, + this.dataLabelText, + this.visibleDataLabelText, + this.bubbleColor, + this.bubbleSizeValue, + this.bubbleRadius, + this.bubblePath, + this.tooltipText, + }); + + /// Contains [sourceDataPath] values. + String primaryKey; + + /// Contains data Source index. + int dataIndex; + + /// Specifies an item index in the data source list. + int actualIndex; + + /// Contains the index of the shape or bubble legend. + int legendMapperIndex; + + /// Contains pixel points. + List> pixelPoints; + + /// Contains coordinate points. + List> rawPoints; + + /// Option to select the particular shape + /// based on the position of the tap or click. + /// + /// Returns `true` when the shape is selected else it will be `false`. + /// + /// See also: + /// * [MapSelectionSettings] option to customize the shape selection. + bool isSelected = false; + + /// Contains data source Label text. + String dataLabelText; + + /// Use this text while zooming. + String visibleDataLabelText; + + /// Contains the tooltip text. + String tooltipText; + + /// Contains data source color. + Color shapeColor; + + /// Contains the actual shape color value. + dynamic shapeColorValue; + + /// Contains the shape path. + Path shapePath; + + // Center of shape path which is used to position the data labels. + Offset shapePathCenter; + + // Width for smart position the data labels. + double shapeWidth; + + /// Contains data source bubble color. + Color bubbleColor; + + /// Contains data source bubble size. + double bubbleSizeValue; + + /// Contains data source bubble radius. + double bubbleRadius; + + /// Contains the bubble path. + Path bubblePath; + + void reset() { + dataIndex = null; + isSelected = false; + dataLabelText = null; + visibleDataLabelText = null; + tooltipText = null; + shapeColor = null; + shapeColorValue = null; + shapePath = null; + shapePathCenter = null; + shapeWidth = null; + bubbleColor = null; + bubbleSizeValue = null; + bubbleRadius = null; + bubblePath = null; + } +} + +class MapIconShape { + const MapIconShape(); + + /// Returns the size based on the value passed to it. + Size getPreferredSize(Size iconSize, SfMapsThemeData themeData) { + return iconSize; + } + + /// Paints the shapes based on the value passed to it. + void paint( + PaintingContext context, + Offset offset, { + RenderBox parentBox, + SfMapsThemeData themeData, + Size iconSize, + Color color, + Color strokeColor, + double strokeWidth, + MapIconType iconType, + }) { + iconSize = getPreferredSize(iconSize, themeData); + final double halfIconWidth = iconSize.width / 2; + final double halfIconHeight = iconSize.height / 2; + final bool hasStroke = strokeWidth != null && + strokeWidth > 0 && + strokeColor != null && + strokeColor != Colors.transparent; + final Paint paint = Paint() + ..isAntiAlias = true + ..color = color; + Path path; + + switch (iconType) { + case MapIconType.circle: + final Rect rect = Rect.fromLTWH( + offset.dx, offset.dy, iconSize.width, iconSize.height); + context.canvas.drawOval(rect, paint); + if (hasStroke) { + paint + ..strokeWidth = strokeWidth + ..color = strokeColor + ..style = PaintingStyle.stroke; + context.canvas.drawOval(rect, paint); + } + break; + case MapIconType.rectangle: + final Rect rect = Rect.fromLTWH( + offset.dx, offset.dy, iconSize.width, iconSize.height); + context.canvas.drawRect(rect, paint); + if (hasStroke) { + paint + ..strokeWidth = strokeWidth + ..color = strokeColor + ..style = PaintingStyle.stroke; + context.canvas.drawRect(rect, paint); + } + break; + case MapIconType.triangle: + path = Path() + ..moveTo(offset.dx + halfIconWidth, offset.dy) + ..lineTo(offset.dx + iconSize.width, offset.dy + iconSize.height) + ..lineTo(offset.dx, offset.dy + iconSize.height) + ..close(); + context.canvas.drawPath(path, paint); + if (hasStroke) { + paint + ..strokeWidth = strokeWidth + ..color = strokeColor + ..style = PaintingStyle.stroke; + context.canvas.drawPath(path, paint); + } + break; + case MapIconType.diamond: + path = Path() + ..moveTo(offset.dx + halfIconWidth, offset.dy) + ..lineTo(offset.dx + iconSize.width, offset.dy + halfIconHeight) + ..lineTo(offset.dx + halfIconWidth, offset.dy + iconSize.height) + ..lineTo(offset.dx, offset.dy + halfIconHeight) + ..close(); + context.canvas.drawPath(path, paint); + if (hasStroke) { + paint + ..strokeWidth = strokeWidth + ..color = strokeColor + ..style = PaintingStyle.stroke; + context.canvas.drawPath(path, paint); + } + break; + } + } +} diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart b/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart new file mode 100644 index 000000000..a13d66aed --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart @@ -0,0 +1,285 @@ +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../behavior/zoom_pan_behavior.dart'; +import '../controller/default_controller.dart'; +import '../enum.dart'; +import '../settings.dart'; +import '../utils.dart'; + +// ignore_for_file: public_member_api_docs +enum _ToolbarIcon { zoomIn, zoomOut, reset } + +class MapToolbar extends StatelessWidget { + const MapToolbar({ + this.onWillZoom, + this.zoomPanBehavior, + this.controller, + }); + + final WillZoomCallback onWillZoom; + final MapZoomPanBehavior zoomPanBehavior; + final MapController controller; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Align( + alignment: _getDesiredAlignment(), + child: Wrap( + direction: zoomPanBehavior.toolbarSettings.direction, + spacing: 8.0, + children: [ + _ToolbarItem( + controller: controller, + zoomPanBehavior: zoomPanBehavior, + onWillZoom: onWillZoom, + iconData: Icons.add_circle_outline, + icon: _ToolbarIcon.zoomIn, + tooltipText: 'Zoom In', + ), + _ToolbarItem( + controller: controller, + zoomPanBehavior: zoomPanBehavior, + onWillZoom: onWillZoom, + iconData: Icons.remove_circle_outline, + icon: _ToolbarIcon.zoomOut, + tooltipText: 'Zoom Out', + ), + _ToolbarItem( + controller: controller, + zoomPanBehavior: zoomPanBehavior, + onWillZoom: onWillZoom, + iconData: Icons.autorenew, + icon: _ToolbarIcon.reset, + tooltipText: 'Reset', + ), + ], + ), + ), + ); + } + + // ignore: missing_return + AlignmentGeometry _getDesiredAlignment() { + switch (zoomPanBehavior.toolbarSettings.position) { + case MapToolbarPosition.topLeft: + return Alignment.topLeft; + case MapToolbarPosition.topRight: + return Alignment.topRight; + case MapToolbarPosition.bottomLeft: + return Alignment.bottomLeft; + case MapToolbarPosition.bottomRight: + return Alignment.bottomRight; + } + } +} + +class _ToolbarItem extends StatefulWidget { + const _ToolbarItem({ + this.controller, + this.zoomPanBehavior, + this.onWillZoom, + this.iconData, + this.icon, + this.tooltipText, + }); + + final MapController controller; + + final MapZoomPanBehavior zoomPanBehavior; + + final WillZoomCallback onWillZoom; + + final IconData iconData; + + final _ToolbarIcon icon; + + final String tooltipText; + + @override + _ToolbarItemState createState() => _ToolbarItemState(); +} + +class _ToolbarItemState extends State<_ToolbarItem> { + final double _increment = 0.5; + final double _iconSize = 16.0; + final double _defaultShadowRadius = 5.0; + final double _hoveredShadowRadius = 7.0; + final Size _toolbarItemSize = const Size(32.0, 32.0); + + bool _isLightTheme = false; + bool _isHovered = false; + bool _enabled = true; + + void _handleZooming(MapZoomDetails details) { + _updateToolbarItemState(); + } + + void _handleReset() { + _updateToolbarItemState(); + } + + void _handleRefresh() { + _updateToolbarItemState(); + } + + void _updateToolbarItemState() { + bool enabled; + switch (widget.icon) { + case _ToolbarIcon.zoomIn: + enabled = widget.zoomPanBehavior.zoomLevel != + widget.zoomPanBehavior.maxZoomLevel; + break; + case _ToolbarIcon.zoomOut: + enabled = widget.zoomPanBehavior.zoomLevel != + widget.zoomPanBehavior.minZoomLevel; + break; + case _ToolbarIcon.reset: + enabled = widget.zoomPanBehavior.zoomLevel != + widget.zoomPanBehavior.minZoomLevel; + break; + } + + if (mounted && enabled != _enabled) { + setState(() { + _enabled = enabled; + }); + } + } + + @override + void initState() { + if (widget.controller != null) { + widget.controller + ..addZoomingListener(_handleZooming) + ..addResetListener(_handleReset) + ..addRefreshListener(_handleRefresh); + } + _updateToolbarItemState(); + super.initState(); + } + + @override + void dispose() { + if (widget.controller != null) { + widget.controller + ..removeZoomingListener(_handleZooming) + ..removeResetListener(_handleReset) + ..removeRefreshListener(_handleRefresh); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + _isLightTheme = Theme.of(context).brightness == Brightness.light; + return MouseRegion( + onHover: (PointerHoverEvent event) { + if (_enabled) { + setState(() { + _isHovered = true; + }); + } + }, + onExit: (PointerExitEvent event) { + if (_enabled || _isHovered) { + setState(() { + _isHovered = false; + }); + } + }, + child: Container( + height: _toolbarItemSize.height, + width: _toolbarItemSize.width, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(_toolbarItemSize.height / 2), + boxShadow: [ + BoxShadow( + color: const Color.fromARGB(61, 0, 0, 0), + blurRadius: + _isHovered ? _hoveredShadowRadius : _defaultShadowRadius, + offset: Offset(0.0, 2.0), + ), + ], + color: _isLightTheme + ? const Color.fromARGB(255, 250, 250, 250) + : const Color.fromARGB(255, 66, 66, 66), + ), + child: Container( + height: _toolbarItemSize.height, + width: _toolbarItemSize.width, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(_toolbarItemSize.height / 2), + color: _getIconBackgroundColor(), + ), + child: IconButton( + iconSize: _iconSize, + focusColor: Colors.transparent, + 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), + icon: Icon(widget.iconData), + color: widget.zoomPanBehavior.toolbarSettings.iconColor ?? + _isLightTheme + ? const Color.fromRGBO(0, 0, 0, 0.54) + : const Color.fromRGBO(255, 255, 255, 0.54), + onPressed: _enabled + ? () { + _handlePointerUp(); + } + : null, + mouseCursor: + _enabled ? SystemMouseCursors.click : SystemMouseCursors.basic, + tooltip: widget.tooltipText, + ), + ), + ), + ); + } + + Color _getIconBackgroundColor() { + return _isHovered + ? widget.zoomPanBehavior.toolbarSettings.itemHoverColor ?? _isLightTheme + ? const Color.fromRGBO(0, 0, 0, 0.08) + : const Color.fromRGBO(255, 255, 255, 0.12) + : widget.zoomPanBehavior.toolbarSettings.itemBackgroundColor ?? + _isLightTheme + ? const Color.fromRGBO(250, 250, 250, 1) + : const Color.fromRGBO(66, 66, 66, 1); + } + + void _handlePointerUp() { + double newZoomLevel; + switch (widget.icon) { + case _ToolbarIcon.zoomIn: + newZoomLevel = widget.zoomPanBehavior.zoomLevel + _increment; + break; + case _ToolbarIcon.zoomOut: + newZoomLevel = widget.zoomPanBehavior.zoomLevel - _increment; + break; + case _ToolbarIcon.reset: + newZoomLevel = widget.zoomPanBehavior.minZoomLevel; + break; + } + + newZoomLevel = interpolateValue( + newZoomLevel, + widget.zoomPanBehavior.minZoomLevel, + widget.zoomPanBehavior.maxZoomLevel); + final MapZoomDetails details = MapZoomDetails( + previousZoomLevel: widget.zoomPanBehavior.zoomLevel, + newZoomLevel: newZoomLevel, + ); + if (widget.onWillZoom == null || widget.onWillZoom(details)) { + widget.zoomPanBehavior?.onZooming(details); + } + } +} diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart b/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart new file mode 100644 index 000000000..ab20bd51f --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart @@ -0,0 +1,678 @@ +import 'dart:async'; +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/theme.dart'; +import 'package:syncfusion_flutter_maps/maps.dart'; + +import '../behavior/zoom_pan_behavior.dart'; +import '../controller/default_controller.dart'; +import '../enum.dart'; +import '../layer/layer_base.dart'; +import '../layer/shape_layer.dart'; +import '../settings.dart'; +import '../utils.dart'; +import 'shapes.dart'; + +// ignore_for_file: public_member_api_docs +class MapTooltip extends StatefulWidget { + const MapTooltip({ + Key key, + this.mapSource, + this.sublayers, + this.shapeTooltipBuilder, + this.bubbleTooltipBuilder, + this.markerTooltipBuilder, + this.tooltipSettings, + this.themeData, + this.controller, + }) : super(key: key); + + final MapShapeSource mapSource; + final List sublayers; + final IndexedWidgetBuilder shapeTooltipBuilder; + final IndexedWidgetBuilder bubbleTooltipBuilder; + final IndexedWidgetBuilder markerTooltipBuilder; + final MapTooltipSettings tooltipSettings; + final SfMapsThemeData themeData; + final MapController controller; + + @override + _MapTooltipState createState() => _MapTooltipState(); +} + +class _MapTooltipState extends State + with SingleTickerProviderStateMixin { + AnimationController controller; + Widget _child; + bool isDesktop; + + void adoptChild(int index, MapLayerElement element, {int sublayerIndex}) { + switch (element) { + case MapLayerElement.shape: + setState(() { + if (sublayerIndex != null) { + final MapShapeSublayer sublayer = widget.sublayers[sublayerIndex]; + _child = sublayer.shapeTooltipBuilder?.call(context, index); + } else { + _child = widget.shapeTooltipBuilder?.call(context, index); + } + }); + break; + case MapLayerElement.bubble: + setState(() { + if (sublayerIndex != null) { + final MapShapeSublayer sublayer = widget.sublayers[sublayerIndex]; + _child = sublayer.bubbleTooltipBuilder?.call(context, index); + } else { + _child = widget.bubbleTooltipBuilder?.call(context, index); + } + }); + break; + case MapLayerElement.marker: + setState(() { + if (sublayerIndex != null) { + final MapShapeSublayer sublayer = widget.sublayers[sublayerIndex]; + _child = sublayer.markerTooltipBuilder?.call(context, index); + } else { + _child = widget.markerTooltipBuilder?.call(context, index); + } + }); + break; + case MapLayerElement.vector: + setState(() { + if (sublayerIndex != null) { + final MapSublayer sublayer = widget.sublayers[sublayerIndex]; + _child = sublayer.tooltipBuilder?.call(context, index); + } + }); + break; + } + } + + @override + void initState() { + super.initState(); + controller = AnimationController( + vsync: this, duration: const Duration(milliseconds: 200)); + } + + @override + void dispose() { + controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows; + return _MapTooltipRenderObjectWidget( + child: _child, + source: widget.mapSource, + tooltipSettings: widget.tooltipSettings, + themeData: widget.themeData, + state: this, + ); + } +} + +/// A Render object widget which draws tooltip shape. +class _MapTooltipRenderObjectWidget extends SingleChildRenderObjectWidget { + const _MapTooltipRenderObjectWidget({ + Widget child, + this.source, + this.tooltipSettings, + this.themeData, + this.state, + }) : super(child: child); + + final MapShapeSource source; + final MapTooltipSettings tooltipSettings; + final SfMapsThemeData themeData; + final _MapTooltipState state; + + @override + _RenderMapTooltip createRenderObject(BuildContext context) { + return _RenderMapTooltip( + source: source, + tooltipSettings: tooltipSettings, + themeData: themeData, + mediaQueryData: MediaQuery.of(context), + context: context, + state: state, + ); + } + + @override + void updateRenderObject( + BuildContext context, _RenderMapTooltip renderObject) { + renderObject + ..source = source + ..tooltipSettings = tooltipSettings + ..themeData = themeData + ..mediaQueryData = MediaQuery.of(context) + ..context = context; + } +} + +class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { + _RenderMapTooltip({ + MapShapeSource source, + MapTooltipSettings tooltipSettings, + SfMapsThemeData themeData, + MediaQueryData mediaQueryData, + BuildContext context, + _MapTooltipState state, + }) : _source = source, + _tooltipSettings = tooltipSettings, + _themeData = themeData, + _mediaQueryData = mediaQueryData, + _state = state, + context = context { + _scaleAnimation = + CurvedAnimation(parent: _state.controller, curve: Curves.easeOutBack); + } + + static const double tooltipTriangleHeight = 7; + final _MapTooltipState _state; + final _TooltipShape _tooltipShape = const _TooltipShape(); + final Duration _waitDuration = const Duration(seconds: 3); + final Duration _hideDeferDuration = const Duration(milliseconds: 500); + Animation _scaleAnimation; + Timer _showTimer; + Timer _hideDeferTimer; + Offset _currentPosition; + MapLayerElement _previousElement; + int _previousSublayerIndex; + int _previousElementIndex; + Rect _elementRect; + bool _preferTooltipOnTop = true; + bool _shouldCalculateTooltipPosition = false; + + BuildContext context; + + MapTooltipSettings get tooltipSettings => _tooltipSettings; + MapTooltipSettings _tooltipSettings; + set tooltipSettings(MapTooltipSettings value) { + if (_tooltipSettings == value) { + return; + } + _tooltipSettings = value; + } + + MapShapeSource get source => _source; + MapShapeSource _source; + set source(MapShapeSource value) { + if (_source == value) { + return; + } + _source = value; + hideTooltip(immediately: true); + } + + SfMapsThemeData get themeData => _themeData; + SfMapsThemeData _themeData; + set themeData(SfMapsThemeData value) { + if (_themeData == value) { + return; + } + _themeData = value; + markNeedsPaint(); + } + + MediaQueryData get mediaQueryData => _mediaQueryData; + MediaQueryData _mediaQueryData; + set mediaQueryData(MediaQueryData value) { + if (_mediaQueryData.orientation == value.orientation) { + return; + } + _mediaQueryData = value; + hideTooltip(immediately: true); + } + + void _update( + Offset position, int index, MapLayerElement element, int sublayerIndex) { + if (index != null) { + _state.adoptChild(index, element, sublayerIndex: sublayerIndex); + _currentPosition = position; + if (child != null && child.attached) { + _showTooltip(); + } + } else if (child != null && child.attached) { + _hideDeferTimer?.cancel(); + _hideDeferTimer = Timer(_hideDeferDuration, hideTooltip); + } + } + + void _showTooltip() { + _shouldCalculateTooltipPosition = true; + _hideDeferTimer?.cancel(); + if (_state.isDesktop) { + if (_state.controller.value == 0 || + _state.controller.status == AnimationStatus.reverse) { + _state.controller.forward(from: 0.0); + return; + } + markNeedsPaint(); + } else { + _state.controller.forward(from: 0.0); + _showTimer?.cancel(); + _showTimer = Timer(_waitDuration, hideTooltip); + } + } + + @override + void hideTooltip({bool immediately = false}) { + _previousElement = null; + _previousElementIndex = null; + _previousSublayerIndex = null; + _showTimer?.cancel(); + immediately ? _state.controller.reset() : _state.controller.reverse(); + } + + void _updateTooltipIfNeeded(Offset position, int index, Rect elementRect, + MapLayerElement element, int sublayerIndex) { + if (sublayerIndex == _previousSublayerIndex && + element != MapLayerElement.shape && + element != MapLayerElement.vector && + _previousElement == element) { + if (_previousElementIndex == index) { + if (_state.isDesktop) { + return; + } + _showTimer?.cancel(); + _showTimer = Timer(_waitDuration, hideTooltip); + return; + } + } + _previousSublayerIndex = sublayerIndex; + _previousElement = element; + _previousElementIndex = index; + // We are storing the element rect to calculate the exact position of the + // bubble or marker in the paint method. + _elementRect = elementRect; + _update(position, index, element, sublayerIndex); + } + + void _handleZooming(MapZoomDetails details) { + hideTooltip(immediately: true); + } + + void _handlePanning(MapPanDetails details) { + hideTooltip(immediately: true); + } + + void _handleReset() { + hideTooltip(immediately: true); + } + + @override + void paintTooltip(int elementIndex, Rect elementRect, MapLayerElement element, + [int sublayerIndex, Offset position]) { + _updateTooltipIfNeeded( + position, elementIndex, elementRect, element, sublayerIndex); + } + + @override + bool get isRepaintBoundary => true; + + @override + void adoptChild(RenderObject child) { + super.adoptChild(child); + _showTooltip(); + } + + @override + void dropChild(RenderObject child) { + super.dropChild(child); + markNeedsPaint(); + } + + @override + void performLayout() { + size = getBoxSize(constraints); + child?.layout(constraints); + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _scaleAnimation.addListener(markNeedsPaint); + if (_state.widget.controller != null) { + _state.widget.controller + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addResetListener(_handleReset); + } + } + + @override + void detach() { + _showTimer?.cancel(); + _hideDeferTimer?.cancel(); + _scaleAnimation.removeListener(markNeedsPaint); + if (_state.widget.controller != null) { + _state.widget.controller + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeResetListener(_handleReset); + } + super.detach(); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (child != null && + child.attached && + (_currentPosition != null || _elementRect != null)) { + // We are calculating the tooltip position in paint method because of the + // child didn't get layouts while forwarding the animation controller. + if (_shouldCalculateTooltipPosition) { + _updateTooltipPositionAndDirection(); + _shouldCalculateTooltipPosition = false; + } + + _tooltipShape.paint( + context, + offset, + _currentPosition, + _preferTooltipOnTop, + this, + _scaleAnimation, + _themeData, + _tooltipSettings); + } + } + + void _updateTooltipPositionAndDirection() { + final double tooltipHeight = child.size.height; + final Rect bounds = Offset.zero & size; + // For bubble and marker. + if (_elementRect != null) { + // Checking tooltip element rect lies inside tooltip render box bounds. + // If not, we are creating a new rect based on the visible area. + if (bounds.overlaps(_elementRect)) { + _updateElementRect(bounds); + } + final double halfRectWidth = _elementRect.width / 2; + Offset tooltipPosition = _elementRect.topLeft + + Offset(halfRectWidth, _elementRect.height * tooltipHeightFactor); + _preferTooltipOnTop = bounds.contains( + tooltipPosition - Offset(0.0, tooltipHeight + tooltipTriangleHeight)); + if (!_preferTooltipOnTop) { + // To get the tooltip position at bottom based on the current rect, + // we had subtracted 1 with the tooltipHeightFactor. + tooltipPosition = _elementRect.topLeft + + Offset( + halfRectWidth, _elementRect.height * (1 - tooltipHeightFactor)); + } + _currentPosition = tooltipPosition; + } + // For shape. + else { + _preferTooltipOnTop = bounds.contains(_currentPosition - + Offset(0.0, tooltipHeight + tooltipTriangleHeight)); + } + } + + void _updateElementRect(Rect bounds) { + double left = _elementRect.left; + double right = _elementRect.right; + double top = _elementRect.top; + double bottom = _elementRect.bottom; + if (_elementRect.left < bounds.left) { + left = bounds.left; + } + if (_elementRect.right > bounds.right) { + right = bounds.right; + } + if (_elementRect.top < bounds.top) { + top = bounds.top; + } + if (_elementRect.bottom > bounds.bottom) { + bottom = bounds.bottom; + } + + _elementRect = Rect.fromLTRB(left, top, right, bottom); + } +} + +/// Base class for map tooltip shapes. +class _TooltipShape { + const _TooltipShape(); + + static const double marginSpace = 6.0; + + /// Paints the tooltip shapes based on the value passed to it. + void paint( + PaintingContext context, + Offset offset, + Offset center, + bool preferTooltipOnTop, + RenderProxyBox parentBox, + Animation tooltipAnimation, + SfMapsThemeData themeData, + MapTooltipSettings tooltipSettings) { + const double tooltipTriangleWidth = 12.0; + const double halfTooltipTriangleWidth = tooltipTriangleWidth / 2; + const double elevation = 0.0; + + Path path = Path(); + final Paint paint = Paint() + ..style = PaintingStyle.fill + ..color = tooltipSettings.color ?? themeData.tooltipColor; + BorderRadius borderRadius = themeData.tooltipBorderRadius; + final double tooltipWidth = parentBox.child.size.width; + double tooltipHeight = parentBox.child.size.height; + final double halfTooltipWidth = tooltipWidth / 2; + double halfTooltipHeight = tooltipHeight / 2; + + double triangleHeight = _RenderMapTooltip.tooltipTriangleHeight; + final double tooltipStartPoint = triangleHeight + tooltipHeight / 2; + double tooltipTriangleOffsetY = tooltipStartPoint - triangleHeight; + + final double endGlobal = parentBox.size.width - marginSpace; + double rightLineWidth = center.dx + halfTooltipWidth > endGlobal + ? endGlobal - center.dx + : halfTooltipWidth; + final double leftLineWidth = center.dx - halfTooltipWidth < marginSpace + ? center.dx - marginSpace + : tooltipWidth - rightLineWidth; + rightLineWidth = leftLineWidth < halfTooltipWidth + ? halfTooltipWidth - leftLineWidth + rightLineWidth + : rightLineWidth; + + double moveNosePoint = leftLineWidth < tooltipWidth * 0.2 + ? tooltipWidth * 0.2 - leftLineWidth + : 0.0; + moveNosePoint = rightLineWidth < tooltipWidth * 0.2 + ? -(tooltipWidth * 0.2 - rightLineWidth) + : moveNosePoint; + + double shiftText = leftLineWidth > rightLineWidth + ? -(halfTooltipWidth - rightLineWidth) + : 0.0; + shiftText = leftLineWidth < rightLineWidth + ? (halfTooltipWidth - leftLineWidth) + : shiftText; + + rightLineWidth = rightLineWidth + elevation; + if (!preferTooltipOnTop) { + // We had multiplied -1 with the below values to move its position from + // top to bottom. + // ________ + // |___ ___| to ___/\___ + // \/ |________| + triangleHeight *= -1; + halfTooltipHeight *= -1; + tooltipTriangleOffsetY *= -1; + tooltipHeight *= -1; + borderRadius = BorderRadius.only( + topRight: Radius.elliptical( + borderRadius.bottomRight.x, -borderRadius.bottomRight.y), + bottomRight: Radius.elliptical( + borderRadius.topRight.x, -borderRadius.topRight.y), + topLeft: Radius.elliptical( + borderRadius.bottomLeft.x, -borderRadius.bottomLeft.y), + bottomLeft: + Radius.elliptical(borderRadius.topLeft.x, -borderRadius.topLeft.y), + ); + } + + path = _getTooltipPath( + path, + triangleHeight, + halfTooltipHeight, + halfTooltipTriangleWidth, + tooltipTriangleOffsetY, + moveNosePoint, + rightLineWidth, + leftLineWidth, + borderRadius, + tooltipHeight); + + context.canvas.save(); + context.canvas + .translate(center.dx, center.dy - triangleHeight - halfTooltipHeight); + context.canvas.scale(tooltipAnimation.value); + context.canvas.drawPath(path, paint); + final Color strokeColor = + tooltipSettings.strokeColor ?? themeData.tooltipStrokeColor; + if (strokeColor != null && strokeColor != Colors.transparent) { + paint + ..color = strokeColor + ..strokeWidth = + tooltipSettings.strokeWidth ?? themeData.tooltipStrokeWidth + ..style = PaintingStyle.stroke; + context.canvas.drawPath(path, paint); + } + + context.canvas.clipPath(path); + context.paintChild( + parentBox.child, offset - _getShiftPosition(offset, center, parentBox)); + context.canvas.restore(); + } + + Path _getTooltipPath( + Path path, + double tooltipTriangleHeight, + double halfTooltipHeight, + double halfTooltipTriangleWidth, + double tooltipTriangleOffsetY, + double moveNosePoint, + double rightLineWidth, + double leftLineWidth, + BorderRadius borderRadius, + double tooltipHeight) { + path.reset(); + + path.moveTo(0, tooltipTriangleHeight + halfTooltipHeight); + // preferTooltipOnTop is true, + // / + + // preferTooltipOnTop is false, + // \ + path.lineTo( + halfTooltipTriangleWidth + moveNosePoint, tooltipTriangleOffsetY); + // preferTooltipOnTop is true, + // ___ + // / + + // preferTooltipOnTop is false, + // \___ + path.lineTo( + rightLineWidth - borderRadius.bottomRight.x, tooltipTriangleOffsetY); + // preferTooltipOnTop is true, + // ___| + // / + + // preferTooltipOnTop is false, + // \___ + // | + path.quadraticBezierTo(rightLineWidth, tooltipTriangleOffsetY, + rightLineWidth, tooltipTriangleOffsetY - borderRadius.bottomRight.y); + path.lineTo(rightLineWidth, + tooltipTriangleOffsetY - tooltipHeight + borderRadius.topRight.y); + // preferTooltipOnTop is true, + // _______ + // ___| + // / + + // preferTooltipOnTop is false, + // \___ + // ________| + path.quadraticBezierTo( + rightLineWidth, + tooltipTriangleOffsetY - tooltipHeight, + rightLineWidth - borderRadius.topRight.x, + tooltipTriangleOffsetY - tooltipHeight); + path.lineTo(-leftLineWidth + borderRadius.topLeft.x, + tooltipTriangleOffsetY - tooltipHeight); + // preferTooltipOnTop is true, + // _______ + // | ___| + // / + + // preferTooltipOnTop is false, + // \___ + // |________| + path.quadraticBezierTo( + -leftLineWidth, + tooltipTriangleOffsetY - tooltipHeight, + -leftLineWidth, + tooltipTriangleOffsetY - tooltipHeight + borderRadius.topLeft.y); + path.lineTo( + -leftLineWidth, tooltipTriangleOffsetY - borderRadius.bottomLeft.y); + // preferTooltipOnTop is true, + // ________ + // |___ ___| + // / + + // preferTooltipOnTop is false, + // ___ \___ + // |________| + path.quadraticBezierTo(-leftLineWidth, tooltipTriangleOffsetY, + -leftLineWidth + borderRadius.bottomLeft.x, tooltipTriangleOffsetY); + path.lineTo( + -halfTooltipTriangleWidth + moveNosePoint, tooltipTriangleOffsetY); + // preferTooltipOnTop is true, + // ________ + // |___ ___| + // \/ + + // preferTooltipOnTop is false, + // ___/\___ + // |________| + path.close(); + + return path; + } + + Offset _getShiftPosition( + Offset offset, Offset center, RenderProxyBox parent) { + final Size childSize = parent.child.size; + final double halfChildWidth = childSize.width / 2; + final double halfChildHeight = childSize.height / 2; + + // Shifting the position of the tooltip to the left side, if its right + // edge goes out of the map's right edge. + if (center.dx + halfChildWidth + marginSpace > parent.size.width) { + return Offset( + 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. + else if (center.dx - halfChildWidth - marginSpace < offset.dx) { + return Offset(center.dx - marginSpace, halfChildHeight); + } + + return Offset(halfChildWidth, halfChildHeight); + } +} diff --git a/packages/syncfusion_flutter_maps/lib/src/common/enum.dart b/packages/syncfusion_flutter_maps/lib/src/enum.dart similarity index 56% rename from packages/syncfusion_flutter_maps/lib/src/common/enum.dart rename to packages/syncfusion_flutter_maps/lib/src/enum.dart index 90f2f6693..f29064ce1 100644 --- a/packages/syncfusion_flutter_maps/lib/src/common/enum.dart +++ b/packages/syncfusion_flutter_maps/lib/src/enum.dart @@ -1,5 +1,3 @@ -part of maps; - /// Position of the legend. enum MapLegendPosition { /// Places the legend at left to the map area. @@ -20,8 +18,8 @@ enum MapIconType { /// Legend's and marker's icon will be drawn in the circle shape. circle, - /// Legend's and marker's icon will be drawn in the square shape. - square, + /// Legend's and marker's icon will be drawn in the rectangle shape. + rectangle, /// Legend's and marker's icon will be drawn in the triangle shape. triangle, @@ -39,17 +37,17 @@ enum MapLegendOverflowMode { wrap, } -/// Behavior of the data labels when it overflowed from the shape. -enum MapLabelOverflowMode { +/// Behavior of the labels when it overflowed from the shape. +enum MapLabelOverflow { /// It hides the overflowed labels. hide, /// It does not make any change even if the labels overflowed. - none, + visible, /// It trims the labels based on the available space in their respective /// shape. - trim + ellipsis } /// Shows legend for the shapes or the bubbles. @@ -59,9 +57,6 @@ enum MapElement { /// [MapElement.bubble], shows legend for the bubbles. bubble, - - /// [MapElement.none], hides the legend. - none } /// Specifies the zooming toolbar position. @@ -79,6 +74,46 @@ enum MapToolbarPosition { bottomRight } -enum _Gesture { scale, pan } +/// Option to place the labels either between the bars or on the bar in bar +/// legend. +enum MapLegendLabelsPlacement { + /// [MapLegendLabelPlacement.Item] places labels in the center of the bar. + onItem, + + /// [MapLegendLabelPlacement.betweenItems] places labels in-between two bars. + betweenItems +} + +/// Placement of edge labels in the bar legend. +enum MapLegendEdgeLabelsPlacement { + /// 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 +} + +/// Specifies the layer for tooltip. +enum MapLayerElement { + /// Shows tooltip for shape layer. + shape, + + /// Shows tooltip for bubble. + bubble, + + /// Shows tooltip for marker. + marker, -enum _Layer { shape, bubble } + /// Shows tooltip for vector layers. + vector, +} + +/// Applies gradient or solid color for the bar segments. +enum MapLegendPaintingStyle { + /// Applies solid color for bar segments. + solid, + + /// Applies gradient color for bar segments. + gradient +} diff --git a/packages/syncfusion_flutter_maps/lib/src/features/maps_legend.dart b/packages/syncfusion_flutter_maps/lib/src/features/maps_legend.dart deleted file mode 100644 index fbaa5ec57..000000000 --- a/packages/syncfusion_flutter_maps/lib/src/features/maps_legend.dart +++ /dev/null @@ -1,449 +0,0 @@ -part of maps; - -/// Visual explanation of the data represented in the map. -/// -/// Each item should contains a shape as well as a label. -class _MapLegend extends StatefulWidget { - const _MapLegend({ - this.dataSource, - this.source, - this.palette, - this.settings, - this.themeData, - this.defaultController, - this.toggleAnimationController, - }); - - final dynamic dataSource; - final MapElement source; - final List palette; - final MapLegendSettings settings; - final SfMapsThemeData themeData; - final _DefaultController defaultController; - final AnimationController toggleAnimationController; - - @override - _MapLegendState createState() => _MapLegendState(); -} - -class _MapLegendState extends State<_MapLegend> { - @override - void didUpdateWidget(_MapLegend oldWidget) { - if (oldWidget.source != widget.source) { - widget.defaultController.currentToggledItemIndex = -1; - widget.defaultController.toggledIndices.clear(); - } - - super.didUpdateWidget(oldWidget); - } - - @override - // ignore: missing_return - Widget build(BuildContext context) { - switch (widget.settings.overflowMode) { - case MapLegendOverflowMode.scroll: - return SingleChildScrollView( - scrollDirection: widget.settings.position == MapLegendPosition.top || - widget.settings.position == MapLegendPosition.bottom - ? Axis.horizontal - : Axis.vertical, - child: actualChild, - ); - case MapLegendOverflowMode.wrap: - return actualChild; - } - } - - Widget get actualChild { - return Padding( - padding: widget.settings.padding, - // Mapped the legend settings properties values to the Wrap widget. - child: Wrap( - direction: widget.settings.direction ?? - widget.settings.position == MapLegendPosition.top || - widget.settings.position == MapLegendPosition.bottom - ? Axis.horizontal - : Axis.vertical, - spacing: widget.settings.itemsSpacing, - children: _getLegendItems(), - runSpacing: 6, - runAlignment: WrapAlignment.center, - alignment: WrapAlignment.start, - ), - ); - } - - /// Returns the list of legend items based on the data source. - List _getLegendItems() { - final List legendItems = []; - final bool isLegendForBubbles = widget.source == MapElement.bubble; - final int paletteLength = - widget.palette != null ? widget.palette.length : 0; - if (widget.dataSource != null && widget.dataSource.isNotEmpty) { - // Here source be either shape color mappers or bubble color mappers. - if (widget.dataSource is List) { - final int length = widget.dataSource.length; - for (int i = 0; i < length; i++) { - final MapColorMapper item = widget.dataSource[i]; - assert(item != null); - final String text = - item.text ?? item.value ?? '${item.from} - ${item.to}'; - assert(text != null); - legendItems.add(_getLegendItem( - text, - item.color, - i, - paletteLength, - isLegendForBubbles, - )); - } - } else { - // Here source is map data source. - widget.dataSource.forEach((String key, _MapModel mapModel) { - assert(mapModel.primaryKey != null); - legendItems.add(_getLegendItem( - mapModel.primaryKey, - isLegendForBubbles ? mapModel.bubbleColor : mapModel.shapeColor, - mapModel.legendMapperIndex, - paletteLength, - isLegendForBubbles, - )); - }); - } - } - - return legendItems; - } - - /// Returns the legend icon and label. - Widget _getLegendItem(String text, Color color, int index, int paletteLength, - bool isLegendForBubbles) { - assert(text != null); - return _MapLegendItem( - index: index, - text: text, - iconShapeColor: color ?? - (isLegendForBubbles - ? widget.themeData.bubbleColor - : (paletteLength > 0 - ? widget.palette[index % paletteLength] - : widget.themeData.layerColor)), - source: widget.source, - legendSettings: widget.settings, - themeData: widget.themeData, - defaultController: widget.defaultController, - toggleAnimationController: widget.toggleAnimationController, - ); - } -} - -class _MapLegendItem extends LeafRenderObjectWidget { - const _MapLegendItem({ - this.index, - this.text, - this.iconShapeColor, - this.source, - this.legendSettings, - this.themeData, - this.defaultController, - this.toggleAnimationController, - }); - - final int index; - final String text; - final Color iconShapeColor; - final MapElement source; - final MapLegendSettings legendSettings; - final SfMapsThemeData themeData; - final _DefaultController defaultController; - final AnimationController toggleAnimationController; - - @override - RenderObject createRenderObject(BuildContext context) { - return _RenderLegendItem( - index: index, - text: text, - iconShapeColor: iconShapeColor, - source: source, - legendSettings: legendSettings, - themeData: themeData, - defaultController: defaultController, - toggleAnimationController: toggleAnimationController, - mediaQueryData: MediaQuery.of(context), - ); - } - - @override - void updateRenderObject( - BuildContext context, _RenderLegendItem renderObject) { - renderObject - ..text = text - ..iconShapeColor = iconShapeColor - ..source = source - ..legendSettings = legendSettings - ..themeData = themeData - ..defaultController = defaultController - ..mediaQueryData = MediaQuery.of(context); - } -} - -class _RenderLegendItem extends RenderBox implements MouseTrackerAnnotation { - _RenderLegendItem({ - int index, - String text, - Color iconShapeColor, - MapElement source, - MapLegendSettings legendSettings, - SfMapsThemeData themeData, - _DefaultController defaultController, - AnimationController toggleAnimationController, - MediaQueryData mediaQueryData, - }) : _index = index, - _text = text, - _iconShapeColor = iconShapeColor, - _source = source, - _legendSettings = legendSettings, - _themeData = themeData, - defaultController = defaultController, - _toggleAnimationController = toggleAnimationController, - _mediaQueryData = mediaQueryData { - _textPainter = TextPainter(textDirection: TextDirection.ltr); - _updateTextPainter(); - _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; - _showLegendIcon = - _legendSettings.iconSize != null && _legendSettings.showIcon; - _toggleColorAnimation = CurvedAnimation( - parent: _toggleAnimationController, curve: Curves.easeInOut); - _iconColorTween = ColorTween(); - _textOpacityTween = Tween(); - _updateToggledIconColor(); - } - - final int _spacing = 3; - - final int _index; - - final double _toggledTextOpacity = 0.5; - - final double _untoggledTextOpacity = 1.0; - - final _MapIconShape _iconShape = const _MapIconShape(); - - final AnimationController _toggleAnimationController; - - _DefaultController defaultController; - - bool _wasToggled = false; - - bool _showLegendIcon; - - TextPainter _textPainter; - - TapGestureRecognizer _tapGestureRecognizer; - - Animation _toggleColorAnimation; - - Tween _textOpacityTween; - - ColorTween _iconColorTween; - - Color _toggledIconColor; - - String get text => _text; - String _text; - set text(String value) { - if (_text == value) { - return; - } - _text = value; - _updateTextPainter(); - markNeedsLayout(); - } - - Color get iconShapeColor => _iconShapeColor; - Color _iconShapeColor; - set iconShapeColor(Color value) { - if (_iconShapeColor == value) { - return; - } - _iconShapeColor = value; - markNeedsPaint(); - } - - MapElement get source => _source; - MapElement _source; - set source(MapElement value) { - if (_source == value) { - return; - } - _source = value; - _wasToggled = false; - markNeedsPaint(); - } - - MapLegendSettings get legendSettings => _legendSettings; - MapLegendSettings _legendSettings; - set legendSettings(MapLegendSettings value) { - if (_legendSettings == value) { - return; - } - _legendSettings = value; - _updateTextPainter(); - _showLegendIcon = - _legendSettings.iconSize != null && _legendSettings.showIcon; - markNeedsLayout(); - } - - SfMapsThemeData get themeData => _themeData; - SfMapsThemeData _themeData; - set themeData(SfMapsThemeData value) { - if (_themeData == value) { - return; - } - _themeData = value; - _updateToggledIconColor(); - markNeedsPaint(); - } - - MediaQueryData get mediaQueryData => _mediaQueryData; - MediaQueryData _mediaQueryData; - set mediaQueryData(MediaQueryData value) { - if (_mediaQueryData == value) { - return; - } - _mediaQueryData = value; - _updateTextPainter(); - markNeedsLayout(); - } - - @override - MouseCursor get cursor => _legendSettings.enableToggleInteraction - ? SystemMouseCursors.click - : SystemMouseCursors.basic; - - @override - PointerEnterEventListener get onEnter => null; - - @override - PointerHoverEventListener get onHover => null; - - @override - PointerExitEventListener get onExit => null; - - void _handleTapUp(TapUpDetails details) { - _wasToggled = !defaultController.toggledIndices.contains(_index); - if (_wasToggled) { - defaultController.toggledIndices.add(_index); - _iconColorTween.begin = _iconShapeColor; - _iconColorTween.end = _toggledIconColor; - _textOpacityTween.begin = _untoggledTextOpacity; - _textOpacityTween.end = _toggledTextOpacity; - } else { - defaultController.toggledIndices.remove(_index); - _iconColorTween.begin = _toggledIconColor; - _iconColorTween.end = _iconShapeColor; - _textOpacityTween.begin = _toggledTextOpacity; - _textOpacityTween.end = _untoggledTextOpacity; - } - defaultController.currentToggledItemIndex = _index; - _toggleAnimationController.forward(from: 0); - } - - void _updateTextPainter() { - _textPainter.textScaleFactor = _mediaQueryData.textScaleFactor; - _textPainter.text = TextSpan(text: _text, style: legendSettings.textStyle); - _textPainter.layout(); - } - - void _updateToggledIconColor() { - _toggledIconColor = _themeData.toggledItemColor != Colors.transparent - ? _themeData.toggledItemColor - .withOpacity(_legendSettings.toggledItemOpacity) - : (_themeData.brightness != Brightness.dark - ? Color.fromRGBO(230, 230, 230, 1.0) - : Color.fromRGBO(66, 66, 66, 1.0)); - } - - @override - bool get isRepaintBoundary => true; - - @override - bool hitTestSelf(Offset position) => _legendSettings.enableToggleInteraction; - - @override - void handleEvent(PointerEvent event, HitTestEntry entry) { - if (_legendSettings.enableToggleInteraction && - event.down && - event is PointerDownEvent) { - _tapGestureRecognizer.addPointer(event); - } - } - - @override - void attach(PipelineOwner owner) { - super.attach(owner); - _toggleAnimationController?.addListener(markNeedsPaint); - } - - @override - void detach() { - _toggleAnimationController?.removeListener(markNeedsPaint); - super.detach(); - } - - @override - void performLayout() { - final double width = - (_showLegendIcon ? _legendSettings.iconSize.width : 0) + - _spacing + - _textPainter.width; - final double height = max( - _showLegendIcon ? _legendSettings.iconSize.height : 0, - _textPainter.height) + - _spacing; - size = Size(width, height); - } - - @override - void paint(PaintingContext context, Offset offset) { - double toggledLegendItemOpacity; - Color iconColor; - Offset actualOffset; - if (_wasToggled || defaultController.currentToggledItemIndex == _index) { - if (defaultController.currentToggledItemIndex == _index) { - iconColor = _iconColorTween.evaluate(_toggleColorAnimation); - toggledLegendItemOpacity = - _textOpacityTween.evaluate(_toggleColorAnimation); - } else { - iconColor = _toggledIconColor; - toggledLegendItemOpacity = _toggledTextOpacity; - } - } else { - iconColor = _iconShapeColor; - toggledLegendItemOpacity = _untoggledTextOpacity; - } - if (_showLegendIcon) { - final Size halfIconSize = - _iconShape.getPreferredSize(_legendSettings.iconSize, _themeData) / 2; - actualOffset = - offset + Offset(0, (size.height - (halfIconSize.height * 2)) / 2); - _iconShape.paint(context, actualOffset, - parentBox: this, - iconSize: _legendSettings.iconSize, - color: iconColor ?? Colors.transparent, - iconType: _legendSettings.iconType); - } - - _textPainter.text = TextSpan( - style: _legendSettings.textStyle.copyWith( - color: _legendSettings.textStyle.color - .withOpacity(toggledLegendItemOpacity)), - text: _text); - _textPainter.layout(); - actualOffset = offset + - Offset( - (_showLegendIcon ? _legendSettings.iconSize.width : 0.0) + _spacing, - (size.height - _textPainter.height) / 2); - _textPainter.paint(context.canvas, actualOffset); - } -} diff --git a/packages/syncfusion_flutter_maps/lib/src/features/maps_toolbar.dart b/packages/syncfusion_flutter_maps/lib/src/features/maps_toolbar.dart deleted file mode 100644 index 0970ce9bd..000000000 --- a/packages/syncfusion_flutter_maps/lib/src/features/maps_toolbar.dart +++ /dev/null @@ -1,242 +0,0 @@ -part of maps; - -enum _ToolbarIcon { zoomIn, zoomOut, reset } - -class _ToolbarItemModel { - _ToolbarItemModel([this.tooltipText, this.iconData, this.enabled]); - - String tooltipText; - - IconData iconData; - - bool enabled; -} - -class _MapToolbar extends StatefulWidget { - const _MapToolbar({ - this.onWillZoom, - this.zoomPanBehavior, - this.defaultController, - }); - - final WillZoomCallback onWillZoom; - final MapZoomPanBehavior zoomPanBehavior; - final _DefaultController defaultController; - - @override - _MapToolbarState createState() => _MapToolbarState(); -} - -class _MapToolbarState extends State<_MapToolbar> { - final double _increment = 0.5; - final double _iconSize = 16.0; - final double _itemSpacing = 8.0; - final Size _toolbarItemSize = const Size(32.0, 32.0); - final double _defaultShadowRadius = 5.0; - final double _hoveredShadowRadius = 7.0; - bool _isLightTheme = false; - _ToolbarIcon _hoveredIcon; - Map<_ToolbarIcon, _ToolbarItemModel> _slotToToolbarItem; - - void _handleZooming(MapZoomDetails details) { - _updateToolbarItemState(); - } - - void _handleReset() { - _updateToolbarItemState(); - } - - void _updateToolbarItemState() { - _slotToToolbarItem[_ToolbarIcon.zoomIn].enabled = - widget.zoomPanBehavior.zoomLevel == widget.zoomPanBehavior.maxZoomLevel - ? false - : true; - _slotToToolbarItem[_ToolbarIcon.zoomOut].enabled = - widget.zoomPanBehavior.zoomLevel == widget.zoomPanBehavior.minZoomLevel - ? false - : true; - _slotToToolbarItem[_ToolbarIcon.reset].enabled = - widget.zoomPanBehavior.zoomLevel == widget.zoomPanBehavior.minZoomLevel - ? false - : true; - if (mounted) { - setState(() { - // Rebuilds to visually update the toolbar items - // based on the current zoom level. - }); - } - } - - @override - void initState() { - _slotToToolbarItem = <_ToolbarIcon, _ToolbarItemModel>{ - _ToolbarIcon.zoomIn: - _ToolbarItemModel('Zoom In', Icons.add_circle_outline), - _ToolbarIcon.zoomOut: - _ToolbarItemModel('Zoom Out', Icons.remove_circle_outline), - _ToolbarIcon.reset: _ToolbarItemModel('Reset', Icons.autorenew), - }; - widget.defaultController.addZoomingListener(_handleZooming); - widget.defaultController.addResetListener(_handleReset); - _updateToolbarItemState(); - super.initState(); - } - - @override - void dispose() { - if (widget.defaultController != null) { - widget.defaultController.removeZoomingListener(_handleZooming); - widget.defaultController.removeResetListener(_handleReset); - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - _isLightTheme = Theme.of(context).brightness == Brightness.light; - return Padding( - padding: EdgeInsets.all(8.0), - child: Align( - alignment: _getDesiredAlignment(), - child: Wrap( - direction: widget.zoomPanBehavior.toolbarSettings.direction, - spacing: _itemSpacing, - children: [ - _getToolbarItem(_ToolbarIcon.zoomIn), - _getToolbarItem(_ToolbarIcon.zoomOut), - _getToolbarItem(_ToolbarIcon.reset), - ], - ), - ), - ); - } - - // ignore: missing_return - AlignmentGeometry _getDesiredAlignment() { - switch (widget.zoomPanBehavior.toolbarSettings.position) { - case MapToolbarPosition.topLeft: - return Alignment.topLeft; - case MapToolbarPosition.topRight: - return Alignment.topRight; - case MapToolbarPosition.bottomLeft: - return Alignment.bottomLeft; - case MapToolbarPosition.bottomRight: - return Alignment.bottomRight; - } - } - - Widget _getToolbarItem(_ToolbarIcon toolbarItem) { - final _ToolbarItemModel item = _slotToToolbarItem[toolbarItem]; - return MouseRegion( - onHover: (PointerHoverEvent event) { - if (item.enabled) { - setState(() { - _hoveredIcon = toolbarItem; - }); - } - }, - onExit: (PointerExitEvent event) { - if (item.enabled || _hoveredIcon != null) { - setState(() { - _hoveredIcon = null; - }); - } - }, - child: Listener( - behavior: HitTestBehavior.opaque, - onPointerUp: (PointerUpEvent event) => _handlePointerUp(toolbarItem), - child: Container( - height: _toolbarItemSize.height, - width: _toolbarItemSize.width, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(_toolbarItemSize.height / 2), - boxShadow: [ - BoxShadow( - color: const Color.fromARGB(61, 0, 0, 0), - blurRadius: _hoveredIcon == toolbarItem - ? _hoveredShadowRadius - : _defaultShadowRadius, - offset: Offset(0.0, 2.0), - ), - ], - color: _isLightTheme - ? const Color.fromARGB(255, 250, 250, 250) - : const Color.fromARGB(255, 66, 66, 66), - ), - child: Container( - height: _toolbarItemSize.height, - width: _toolbarItemSize.width, - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(_toolbarItemSize.height / 2), - color: _getIconBackgroundColor(toolbarItem), - ), - child: IconButton( - iconSize: _iconSize, - focusColor: Colors.transparent, - 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), - icon: Icon(item.iconData), - color: widget.zoomPanBehavior.toolbarSettings.iconColor ?? - _isLightTheme - ? const Color.fromRGBO(0, 0, 0, 0.54) - : const Color.fromRGBO(255, 255, 255, 0.54), - onPressed: item.enabled - ? () { - _handlePointerUp(toolbarItem); - } - : null, - mouseCursor: item.enabled - ? SystemMouseCursors.click - : SystemMouseCursors.basic, - tooltip: item.tooltipText, - ), - ), - ), - )); - } - - Color _getIconBackgroundColor(_ToolbarIcon toolbarItem) { - return _hoveredIcon == toolbarItem - ? widget.zoomPanBehavior.toolbarSettings.itemHoverColor ?? - (_isLightTheme - ? const Color.fromRGBO(0, 0, 0, 0.08) - : const Color.fromRGBO(255, 255, 255, 0.12)) - : widget.zoomPanBehavior.toolbarSettings.itemBackgroundColor ?? - (_isLightTheme - ? const Color.fromRGBO(250, 250, 250, 1) - : const Color.fromRGBO(66, 66, 66, 1)); - } - - void _handlePointerUp(_ToolbarIcon toolbarItem) { - double newZoomLevel; - switch (toolbarItem) { - case _ToolbarIcon.zoomIn: - newZoomLevel = widget.zoomPanBehavior.zoomLevel + _increment; - break; - case _ToolbarIcon.zoomOut: - newZoomLevel = widget.zoomPanBehavior.zoomLevel - _increment; - break; - case _ToolbarIcon.reset: - newZoomLevel = widget.zoomPanBehavior.minZoomLevel; - break; - } - - newZoomLevel = _interpolateValue( - newZoomLevel, - widget.zoomPanBehavior.minZoomLevel, - widget.zoomPanBehavior.maxZoomLevel); - final MapZoomDetails details = MapZoomDetails( - previousZoomLevel: widget.zoomPanBehavior.zoomLevel, - newZoomLevel: newZoomLevel, - ); - if (widget.onWillZoom == null || widget.onWillZoom(details)) { - widget.zoomPanBehavior?.onZooming(details); - } - _updateToolbarItemState(); - } -} diff --git a/packages/syncfusion_flutter_maps/lib/src/features/maps_tooltip.dart b/packages/syncfusion_flutter_maps/lib/src/features/maps_tooltip.dart deleted file mode 100644 index 859145f79..000000000 --- a/packages/syncfusion_flutter_maps/lib/src/features/maps_tooltip.dart +++ /dev/null @@ -1,426 +0,0 @@ -part of maps; - -/// Shows additional information about a particular shape in maps. -class _MapTooltip extends StatefulWidget { - const _MapTooltip({ - Key key, - this.mapDelegate, - this.shapeTooltipBuilder, - this.bubbleTooltipBuilder, - this.enableShapeTooltip, - this.enableBubbleTooltip, - this.tooltipSettings, - this.themeData, - this.defaultController, - }) : super(key: key); - - final MapShapeLayerDelegate mapDelegate; - final IndexedWidgetBuilder shapeTooltipBuilder; - final IndexedWidgetBuilder bubbleTooltipBuilder; - final bool enableShapeTooltip; - final bool enableBubbleTooltip; - final MapTooltipSettings tooltipSettings; - final SfMapsThemeData themeData; - final _DefaultController defaultController; - - @override - _MapTooltipState createState() => _MapTooltipState(); -} - -class _MapTooltipState extends State<_MapTooltip> - with SingleTickerProviderStateMixin { - AnimationController tooltipAnimationController; - - Widget _tooltipChild; - - // Specifies index based on the shape layer data count. - void addChild(int index, _Layer layer) { - switch (layer) { - case _Layer.shape: - _tooltipChild = widget.shapeTooltipBuilder(context, index); - setState(() { - // Rebuilds to visually add a builder for shape tooltip when a - // shape is tapped or hovered. - }); - break; - case _Layer.bubble: - _tooltipChild = widget.bubbleTooltipBuilder(context, index); - setState(() { - // Rebuilds to visually add a builder for bubble tooltip when a - // bubble is tapped or hovered. - }); - break; - } - } - - void removeChild() { - _tooltipChild = null; - setState(() { - // Rebuilds to visually remove the tooltip child from the tooltip widget. - }); - } - - @override - void initState() { - super.initState(); - tooltipAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 200)); - } - - @override - void dispose() { - tooltipAnimationController?.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return _MapTooltipRenderObjectWidget( - key: widget.key, - child: _tooltipChild, - delegate: widget.mapDelegate, - enableShapeTooltip: widget.enableShapeTooltip, - enableBubbleTooltip: widget.enableBubbleTooltip, - tooltipSettings: widget.tooltipSettings, - themeData: widget.themeData, - state: this, - ); - } -} - -/// A Render object widget which draws tooltip shape. -class _MapTooltipRenderObjectWidget extends SingleChildRenderObjectWidget { - const _MapTooltipRenderObjectWidget({ - Key key, - Widget child, - this.delegate, - this.enableShapeTooltip, - this.enableBubbleTooltip, - this.tooltipSettings, - this.themeData, - this.state, - }) : super(key: key, child: child); - - final MapShapeLayerDelegate delegate; - final bool enableShapeTooltip; - final bool enableBubbleTooltip; - final MapTooltipSettings tooltipSettings; - final SfMapsThemeData themeData; - final _MapTooltipState state; - - @override - _RenderMapTooltip createRenderObject(BuildContext context) { - return _RenderMapTooltip( - delegate: delegate, - enableShapeTooltip: enableShapeTooltip, - enableBubbleTooltip: enableBubbleTooltip, - tooltipSettings: tooltipSettings, - themeData: themeData, - context: context, - mediaQueryData: MediaQuery.of(context), - state: state, - ); - } - - @override - void updateRenderObject( - BuildContext context, _RenderMapTooltip renderObject) { - renderObject - ..delegate = delegate - ..enableShapeTooltip = enableShapeTooltip - ..enableBubbleTooltip = enableBubbleTooltip - ..tooltipSettings = tooltipSettings - ..themeData = themeData - ..context = context - ..mediaQueryData = MediaQuery.of(context); - } -} - -class _RenderMapTooltip extends _ShapeLayerChildRenderBoxBase { - _RenderMapTooltip({ - MapShapeLayerDelegate delegate, - bool enableShapeTooltip, - bool enableBubbleTooltip, - MapTooltipSettings tooltipSettings, - SfMapsThemeData themeData, - BuildContext context, - MediaQueryData mediaQueryData, - _MapTooltipState state, - }) : _delegate = delegate, - _enableShapeTooltip = enableShapeTooltip, - _enableBubbleTooltip = enableBubbleTooltip, - _tooltipSettings = tooltipSettings, - _themeData = themeData, - _mediaQueryData = mediaQueryData, - _state = state, - context = context { - _textPainter = TextPainter(textDirection: TextDirection.ltr); - _textPainter.textScaleFactor = _mediaQueryData.textScaleFactor; - _tooltipAnimation = CurvedAnimation( - parent: _state.tooltipAnimationController, curve: Curves.easeOutBack); - } - - final _MapTooltipState _state; - final _TooltipShape _tooltipShape = const _TooltipShape(); - Animation _tooltipAnimation; - Timer _tooltipDelayTimer; - TextPainter _textPainter; - String _tooltipText; - Offset _touchPosition; - BuildContext context; - - bool get drawTooltip => - (_tooltipText != null || child != null) && _touchPosition != null; - - MapTooltipSettings get tooltipSettings => _tooltipSettings; - MapTooltipSettings _tooltipSettings; - set tooltipSettings(MapTooltipSettings value) { - if (_tooltipSettings == value) { - return; - } - _tooltipSettings = value; - } - - MapShapeLayerDelegate get delegate => _delegate; - MapShapeLayerDelegate _delegate; - set delegate(MapShapeLayerDelegate value) { - if (_delegate == value) { - return; - } - _delegate = value; - _tooltipText = null; - // Tooltip will remain in the UI for 3 seconds. - // Between this time when mapDelegate changes, - // We need to remove the tooltip from the UI immediately. - if (_state.tooltipAnimationController.value > 0) { - _state.tooltipAnimationController.reverse(); - } - } - - bool get enableShapeTooltip => _enableShapeTooltip; - bool _enableShapeTooltip; - set enableShapeTooltip(bool value) { - if (_enableShapeTooltip == value) { - return; - } - _enableShapeTooltip = value; - } - - bool get enableBubbleTooltip => _enableBubbleTooltip; - bool _enableBubbleTooltip; - set enableBubbleTooltip(bool value) { - if (_enableBubbleTooltip == value) { - return; - } - _enableBubbleTooltip = value; - } - - SfMapsThemeData get themeData => _themeData; - SfMapsThemeData _themeData; - set themeData(SfMapsThemeData value) { - if (_themeData == value) { - return; - } - _themeData = value; - markNeedsPaint(); - } - - MediaQueryData get mediaQueryData => _mediaQueryData; - MediaQueryData _mediaQueryData; - set mediaQueryData(MediaQueryData value) { - if (_mediaQueryData == value) { - return; - } - _mediaQueryData = value; - _tooltipText = null; - _touchPosition = null; - _textPainter.textScaleFactor = _mediaQueryData.textScaleFactor; - } - - void _updateTooltipTextAndPosition( - Offset position, _MapModel model, _Layer layer, - {bool isHover = false}) { - bool hasChild = false; - String tooltipText; - if (model != null) { - if (_enableBubbleTooltip && layer == _Layer.bubble) { - if (model.dataIndex != null && - _state.widget.bubbleTooltipBuilder != null) { - _state.addChild(model.dataIndex, layer); - hasChild = true; - } else if (model.dataIndex != null && - _delegate.bubbleTooltipTextMapper != null) { - tooltipText = _delegate.bubbleTooltipTextMapper(model.dataIndex); - } else { - tooltipText = model.primaryKey; - } - _touchPosition = - tooltipText != null || hasChild ? position : _touchPosition; - } else if (_enableShapeTooltip && layer == _Layer.shape) { - if (model.dataIndex != null && - _state.widget.shapeTooltipBuilder != null) { - _state.addChild(model.dataIndex, layer); - hasChild = true; - } else if (model.dataIndex != null && - _delegate.shapeTooltipTextMapper != null) { - tooltipText = _delegate.shapeTooltipTextMapper(model.dataIndex); - } else { - tooltipText = model.primaryKey; - } - _touchPosition = - tooltipText != null || hasChild ? position : _touchPosition; - } - } - - if (isHover) { - if (model != null && (tooltipText != null || hasChild)) { - if (tooltipText != null && child != null) { - _state.removeChild(); - } - - _tooltipDelayTimer?.cancel(); - if (_state.tooltipAnimationController.value == 0) { - _state.tooltipAnimationController.forward(from: 0.0); - _touchPosition = position; - _tooltipText = tooltipText; - } else { - _tooltipText = tooltipText; - markNeedsPaint(); - } - } else if (_state.tooltipAnimationController.value > 0) { - _tooltipDelayTimer?.cancel(); - _tooltipDelayTimer = Timer( - const Duration(milliseconds: 300), - () { - _tooltipDelayTimer = null; - if (tooltipText == null) { - _state.tooltipAnimationController.reverse(); - } - }, - ); - } - } else if (model != null && (tooltipText != null || hasChild)) { - if (tooltipText != null && child != null) { - _state.removeChild(); - } - _forwardTooltipAnimation(); - _touchPosition = position; - _tooltipText = tooltipText; - } - } - - void _forwardTooltipAnimation() { - _state.tooltipAnimationController.forward(from: 0.0); - _tooltipDelayTimer?.cancel(); - _tooltipDelayTimer = Timer( - const Duration(seconds: 3), - () { - _tooltipDelayTimer = null; - if (_state.tooltipAnimationController.status == - AnimationStatus.completed) { - _state.tooltipAnimationController.reverse(); - } - }, - ); - } - - void _handleAnimationStatusChange(AnimationStatus status) { - if (status == AnimationStatus.dismissed) { - _tooltipText = null; - _touchPosition = null; - } - } - - void _forceRemoveTooltip() { - if (_tooltipText != null) { - _tooltipDelayTimer?.cancel(); - _tooltipDelayTimer = null; - _tooltipText = null; - _touchPosition = null; - markNeedsPaint(); - } - } - - void _handleZooming(MapZoomDetails details) { - _forceRemoveTooltip(); - } - - void _handlePanning(MapPanDetails details) { - _forceRemoveTooltip(); - } - - void _handleReset() { - _forceRemoveTooltip(); - } - - @override - void onTap(Offset position, {_MapModel model, _Layer layer}) { - _updateTooltipTextAndPosition(position, model, layer); - } - - @override - void onHover(Offset position, {_MapModel model, _Layer layer}) { - _updateTooltipTextAndPosition(position, model, layer, isHover: true); - } - - @override - bool get isRepaintBoundary => true; - - @override - void performLayout() { - size = _getBoxSize(constraints); - if (child != null) { - child.layout(constraints, parentUsesSize: true); - } - } - - @override - void attach(PipelineOwner owner) { - super.attach(owner); - _tooltipAnimation.addListener(markNeedsPaint); - _tooltipAnimation.addStatusListener(_handleAnimationStatusChange); - if (_state.widget.defaultController != null) { - _state.widget.defaultController.addZoomingListener(_handleZooming); - _state.widget.defaultController.addPanningListener(_handlePanning); - _state.widget.defaultController.addResetListener(_handleReset); - } - } - - @override - void detach() { - _tooltipAnimation.removeListener(markNeedsPaint); - _tooltipAnimation.removeStatusListener(_handleAnimationStatusChange); - if (_state.widget.defaultController != null) { - _state.widget.defaultController.removeZoomingListener(_handleZooming); - _state.widget.defaultController.removePanningListener(_handlePanning); - _state.widget.defaultController.removeResetListener(_handleReset); - } - _tooltipDelayTimer?.cancel(); - super.detach(); - } - - @override - void paint(PaintingContext context, Offset offset) { - if (drawTooltip) { - if (_tooltipText != null) { - _textPainter.text = TextSpan( - text: _tooltipText, - style: _tooltipSettings.textStyle ?? _themeData.tooltipTextStyle); - _textPainter.layout(); - } - - _tooltipShape.paint( - context, - offset, - _touchPosition, - _textPainter, - Paint() - ..style = PaintingStyle.fill - ..color = _tooltipSettings.color ?? _themeData.tooltipColor, - this, - _tooltipAnimation, - _themeData, - _tooltipSettings); - } - } -} diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/layer_base.dart b/packages/syncfusion_flutter_maps/lib/src/layer/layer_base.dart new file mode 100644 index 000000000..c8dfc90b7 --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/layer/layer_base.dart @@ -0,0 +1,467 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_maps/src/layer/vector_layers.dart'; + +import '../behavior/zoom_pan_behavior.dart'; +import '../controller/default_controller.dart'; +import '../layer/shape_layer.dart'; +import '../settings.dart'; +import '../utils.dart'; + +/// Base class for the [MapShapeLayer] and [MapTileLayer]. +/// +/// See also: +/// * [MapShapeLayer],[MapTileLayer] for adding in the [SfMaps.layers]. +abstract class MapLayer extends StatelessWidget { + /// Creates a [MapLayer]. + const MapLayer({ + Key key, + this.sublayers, + this.initialMarkersCount, + this.markerBuilder, + this.markerTooltipBuilder, + this.tooltipSettings, + this.zoomPanBehavior, + this.onWillZoom, + this.onWillPan, + }) : super(key: key); + + /// Collection of [MapShapeSublayer], [MapLineLayer], [MapPolylineLayer], + /// [MapPolygonLayer], [MapCircleLayer], and [MapArcLayer]. + /// + /// It is applicable for both the [MapShapeLayer] and [MapTileLayer]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// Container( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ), + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// data.length, + /// (int index) { + /// return MapLine( + /// from: data[index].from, + /// to: data[index].to, + /// dashArray: [8, 3, 4, 2], + /// color: _selectedIndex == index + /// ? Colors.red + /// : Colors.blue, + /// width: 2, + /// onTap: () { + /// setState(() { + /// _selectedIndex = index; + /// }); + /// }); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final List sublayers; + + /// Option to set markers count initially. It cannot be be updated + /// dynamically. + /// + /// The [MapLayer.markerBuilder] callback will be called number of times equal + /// to the value specified in the [MapLayer.initialMarkersCount] property. + /// The default value of the of this property is null. + /// + /// See also: + /// * [markerBuilder], for returning the [MapMarker]. + /// * [MapShapeLayer.controller], [MapTileLayer.controller] for dynamically + /// updating the markers collection. + final int initialMarkersCount; + + /// Returns the [MapMarker] for the given index. + /// + /// Markers which be used to denote the locations on the map. + /// + /// It is possible to use the built-in symbols or display a custom widget at a + /// specific latitude and longitude on a map. + /// + /// The [MapLayer.markerBuilder] callback will be called number of times equal + /// to the value specified in the [MapLayer.initialMarkersCount] property. + /// The default value of the of this property is null. + /// + /// For rendering the custom widget for the marker, pass the required widget + /// for child in [MapMarker] constructor. + /// + /// ```dart + /// List data; + /// + /// @override + /// void initState() { + /// data = const [ + /// Model('Brazil', -14.235004, -51.92528), + /// Model('Germany', 51.16569, 10.451526), + /// Model('Australia', -25.274398, 133.775136), + /// Model('India', 20.593684, 78.96288), + /// Model('Russia', 61.52401, 105.318756) + /// ]; + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// dataCount: data.length, + /// primaryValueMapper: (index) => data[index].country, + /// ), + /// initialMarkersCount: 5, + /// markerBuilder: (BuildContext context, int index){ + /// return MapMarker( + /// latitude: data[index].latitude, + /// longitude: data[index].longitude, + /// ); + /// }, + /// ), + /// ], + /// ), + /// ), + /// ) + /// ), + /// ); + /// } + /// + /// class Model { + /// const Model(this.country, this.latitude, this.longitude); + /// + /// final String country; + /// final double latitude; + /// final double longitude; + /// } + /// ``` + /// See also: + /// * [MapShapeLayerController], for dynamically updating the markers. + /// * [MapMarker], to create a map marker. + final MapMarkerBuilder markerBuilder; + + /// Returns the widget for the tooltip of the [MapMarker]. + /// + /// To show the tooltip for markers, return a customized widget in the + /// [MapLayer.markerTooltipBuilder]. This widget will then be wrapped in the + /// in-built shape which comes with the nose at the bottom. + /// + /// The [MapLayer.markerTooltipBuilder] will be called when the user interacts + /// with the markers i.e., while tapping in touch devices and hovering in the + /// mouse enabled devices. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Padding( + /// padding: EdgeInsets.all(15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// dataCount: worldMapData.length, + /// primaryValueMapper: (int index) => + /// worldMapData[index].primaryKey, + /// ), + /// markerTooltipBuilder: (BuildContext context, int index) { + /// if(index == 0) { + /// return Container( + /// child: Icon(Icons.airplanemode_inactive), + /// ); + /// } + /// else + /// { + /// return Container( + /// child: Icon(Icons.airplanemode_active), + /// ); + /// } + /// }, + /// + /// ), + /// ], + /// ), + /// ), + /// ); + /// } + /// ``` + /// See also: + /// * [MapLayer.tooltipSettings], to customize the color and stroke of the + /// tooltip. + /// * [SfMapsThemeData.tooltipBorderRadius], to customize the corners of the + /// tooltip. + final IndexedWidgetBuilder markerTooltipBuilder; + + /// Customizes the bubble, marker, and shape tooltip's appearance. + /// + /// See also: + /// * [MapShapeLayer.shapeTooltipBuilder], to show the customized tooltip + /// for the shapes. + /// * [MapShapeLayer.bubbleTooltipBuilder], to show the customized tooltip + /// for the bubbles. + /// * [MapShapeLayer.markerTooltipBuilder],[MapTileLayer.markerTooltipBuilder] + /// to show the customized tooltip for the markers. + final MapTooltipSettings tooltipSettings; + + /// Enables zooming and panning in [MapShapeLayer] and [MapTileLayer]. + /// + /// Zooming and panning will start working when the new instance of + /// [MapZoomPanBehavior] is set to [zoomPanBehavior] or + /// [zoomPanBehavior]. However, if you need to restrict pinch + /// zooming or panning for any specific requirements, you can set the + /// [MapZoomPanBehavior.enablePinching] and [MapZoomPanBehavior.enablePanning] + /// properties to false respectively. + /// + /// The [MapZoomPanBehavior.focalLatLng] is the focal point of the map layer + /// based on which zooming happens. + /// + /// The default [MapZoomPanBehavior.zoomLevel] value is 1 which will show the + /// whole map in the viewport for [MapShapeLayer] and the available bounds + /// for the [MapTileLayer] based on the [MapZoomPanBehavior.focalLatLng] + /// (Please check the documentation of [MapTileLayer] to know more details + /// about how [MapZoomPanBehavior.zoomLevel] works in it). + /// + /// You can also get the current zoom level and focal position of the + /// map after the interaction using the [MapZoomPanBehavior.zoomLevel] and + /// [MapZoomPanBehavior.focalLatLng]. These properties can also be set + /// dynamically. + /// + /// The minimum and maximum zoom levels can be restricted using the + /// [MapZoomPanBehavior.minZoomLevel] and [MapZoomPanBehavior.maxZoomLevel] + /// properties respectively. The default values of + /// [MapZoomPanBehavior.minZoomLevel] and [MapZoomPanBehavior.maxZoomLevel] + /// are 0 and 15 respectively. However, for [MapTileLayer], + /// the [MapZoomPanBehavior.maxZoomLevel] may slightly vary depending + /// on the providers. Check the respective official website of the map + /// tile providers to know about the maximum zoom level it supports. + /// + /// By default, there is a toolbar for the zooming operations for the web + /// platform. However, you can change its visibility using the + /// [MapZoomPanBehavior.showToolbar] property. + /// + /// [MapZoomPanBehavior] objects are expected to be long-lived, not recreated + /// with each build. + /// + /// The procedure and the behavior are similar for both the [MapShapeLayer] + /// and [MapTileLayer]. + /// + /// ```dart + /// MapZoomPanBehavior _zoomPanBehavior; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior() + /// ..zoomLevel = 4 + /// ..focalLatLng = MapLatLng(19.0759837, 72.8776559); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Zoom pan'), + /// ), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ), + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final MapZoomPanBehavior zoomPanBehavior; + + /// Called whenever zooming is happening. + /// + /// If it returns false, zooming will not happen. + /// + /// [MapZoomDetails] contains following properties. + /// * [MapZoomDetails.previousVisibleBounds] - provides the visible bounds + /// before the current zooming operation completes i.e. current visible + /// bounds. + /// * [MapZoomDetails.newVisibleBounds] - provides the new visible bounds + /// when the current zoom completes. Hence, if it returns false, + /// there will be no changes in the UI. + /// * [MapZoomDetails.previousZoomLevel] - provides the zoom level + /// before the current zooming operation completes i.e. current zoom + /// level. + /// * [MapZoomDetails.newZoomLevel] - provides the new zoom level + /// when the current zoom completes. Hence, if it returns false, there will + /// be no changes in the UI. + /// * [MapZoomDetails.globalFocalPoint] - The global focal point of the + /// pointers in contact with the screen. + /// * [MapZoomDetails.localFocalPoint] - The local focal point of the + /// pointers in contact with the screen. + final WillZoomCallback onWillZoom; + + /// Called whenever panning is happening. + /// + /// If it returns false, panning will not happen. + /// + /// [MapPanDetails] contains following properties. + /// * [MapPanDetails.previousVisibleBounds] - provides the visible bounds + /// before the current panning operation completes i.e. current visible + /// bounds. + /// * [MapPanDetails.newVisibleBounds] - provides the new visible bounds + /// when the current pan completes. Hence, if it returns false, + /// there will be no changes in the UI. + /// * [MapPanDetails.zoomLevel] - provides the current zoom level. + /// * [MapPanDetails.delta] - The difference in pixels between touch start + /// and current touch position. + /// * [MapPanDetails.globalFocalPoint] - The global focal point of the + /// pointers in contact with the screen. + /// * [MapPanDetails.localFocalPoint] - The local focal point of the + /// pointers in contact with the screen. + final WillPanCallback onWillPan; +} + +/// Base class for all vector shapes like [MapLineLayer], [MapCircleLayer], +/// [MapArcLayer], [MapPolylineLayer], and [MapPolygonLayer]. +abstract class MapSublayer extends StatelessWidget { + /// Creates a [MapSublayer]. + const MapSublayer({Key key, this.tooltipBuilder}) : super(key: key); + + /// Returns a widget for the map line tooltip based on the index. + /// + /// A map line tooltip displays additional information about the purpose of + /// the shape drawn. To show tooltip for the shape return a completely + /// customized widget in [MapSublayer.tooltipBuilder]. + /// + /// The [MapSublayer.tooltipBuilder] callback will be called when the + /// user interacts with the shape. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ), + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// lines.length, + /// (int index) { + /// return MapLine( + /// from: lines[index].from, + /// to: lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// tooltipBuilder: (BuildContext context, int index) { + /// if (index == 0) { + /// return Container( + /// child: Icon(Icons.airplanemode_inactive), + /// ); + /// } + /// else { + /// return Container( + /// child: Icon(Icons.airplanemode_active), + /// ); + /// } + /// }, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final IndexedWidgetBuilder tooltipBuilder; +} + +/// Adds [MapSublayer] into a stack widget. +class SublayerContainer extends Stack { + /// Creates a [SublayerContainer]. + SublayerContainer({ + List children, + this.controller, + this.tooltipKey, + }) : super(children: children ?? []); + + /// Holds the parent's map controller values. + final MapController controller; + + /// Key is used to get tooltip render box. + final GlobalKey tooltipKey; + + @override + RenderStack createRenderObject(BuildContext context) { + return RenderSublayerContainer( + controller: controller, tooltipKey: tooltipKey, context: context); + } +} + +/// Adds [MapSublayer] into a stack widget. +class RenderSublayerContainer extends RenderStack { + /// Creates a [RenderSublayerContainer]. + RenderSublayerContainer({ + this.controller, + this.tooltipKey, + this.context, + }) : super(textDirection: Directionality.of(context)); + + /// Used to connect all sub elements into the parent layer. + final MapController controller; + + /// Key is used to get tooltip render box. + final GlobalKey tooltipKey; + + /// The build context. + final BuildContext context; + + /// Returns the sublayer index. + int getSublayerIndex(MapSublayer sublayer) { + final SublayerContainer sublayerContainer = context.widget; + return sublayerContainer.children.indexOf(sublayer); + } + + @override + void performLayout() { + size = getBoxSize(constraints); + super.performLayout(); + } +} diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/maps_layer.dart b/packages/syncfusion_flutter_maps/lib/src/layer/maps_layer.dart deleted file mode 100644 index 58c18a7b5..000000000 --- a/packages/syncfusion_flutter_maps/lib/src/layer/maps_layer.dart +++ /dev/null @@ -1,1595 +0,0 @@ -part of maps; - -///Base class for the [MapShapeLayer]. -/// -/// See also: -/// * [MapShapeLayer], for adding in [SfMaps.layers]. -abstract class MapLayer extends StatelessWidget { - /// Creates a [MapLayer]. - const MapLayer({ - Key key, - this.initialMarkersCount, - this.markerBuilder, - this.zoomPanBehavior, - this.onWillZoom, - this.onWillPan, - }) : super(key: key); - - /// Option to set markers count initially. It cannot be be updated - /// dynamically. - /// - /// The [MapLayer.markerBuilder] callback will be called number of times equal - /// to the value specified in the [MapLayer.initialMarkersCount] property. - /// The default value of the of this property is null. - /// - /// See also: - /// * [markerBuilder], for returning the [MapMarker]. - /// * [MapShapeLayer.controller], for dynamically updating the markers - /// collection. - final int initialMarkersCount; - - /// Returns the [MapMarker] for the given index. - /// - /// Markers which be used to denote the locations on the map. - /// - /// It is possible to use the built-in symbols or display a custom widget at a - /// specific latitude and longitude on a map. - /// - /// The [MapLayer.markerBuilder] callback will be called number of times equal - /// to the value specified in the [MapLayer.initialMarkersCount] property. - /// The default value of the of this property is null. - /// - /// For rendering the custom widget for the marker, pass the required widget - /// for child in [MapMarker] constructor. - /// - /// ```dart - /// List data; - /// - /// @override - /// void initState() { - /// data = const [ - /// Model('Brazil', -14.235004, -51.92528), - /// Model('Germany', 51.16569, 10.451526), - /// Model('Australia', -25.274398, 133.775136), - /// Model('India', 20.593684, 78.96288), - /// Model('Russia', 61.52401, 105.318756) - /// ]; - /// - /// super.initState(); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: Container( - /// height: 350, - /// child: Padding( - /// padding: EdgeInsets.only(left: 15, right: 15), - /// child: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', - /// shapeDataField: 'name', - /// dataCount: data.length, - /// primaryValueMapper: (index) => data[index].country, - /// ), - /// initialMarkersCount: 5, - /// markerBuilder: (BuildContext context, int index){ - /// return MapMarker( - /// latitude: data[index].latitude, - /// longitude: data[index].longitude, - /// ); - /// }, - /// ), - /// ], - /// ), - /// ), - /// ) - /// ), - /// ); - /// } - /// - /// class Model { - /// const Model(this.country, this.latitude, this.longitude); - /// - /// final String country; - /// final double latitude; - /// final double longitude; - /// } - /// ``` - /// See also: - /// * [MapShapeLayerController], for dynamically updating the markers. - /// * [MapMarker], to create a map marker. - final MapMarkerBuilder markerBuilder; - - /// Enables zooming and panning in [MapShapeLayer] and [MapTileLayer]. - /// - /// Zooming and panning will start working when the new instance of - /// [MapZoomPanBehavior] is set to [MapShapeLayer.zoomPanBehavior]. However, - /// if you need to restrict pinch zooming or panning for any specific - /// requirements, you can set the [MapZoomPanBehavior.enablePinching] and - /// [MapZoomPanBehavior.enablePanning] properties to false respectively. - /// - /// The [MapZoomPanBehavior.focalLatLng] is the focal point of the map layer - /// based on which zooming happens. - /// - /// The default [MapZoomPanBehavior.zoomLevel] value is 1 which will show the - /// whole map in the viewport for [MapShapeLayer] and the possible bounds for - /// the [MapTileLayer] based on the [MapZoomPanBehavior.focalLatLng] - /// (Please check the documentation - /// of [MapTileLayer] to know more details about how - /// [MapZoomPanBehavior.zoomLevel] works in it). - /// - /// You can also get the current zoom level and focal position of the - /// map layer using the [MapZoomPanBehavior.zoomLevel] and - /// [MapZoomPanBehavior.focalLatLng] after the interaction. - /// - /// The minimum and maximum zooming levels can be restricted using the - /// [MapZoomPanBehavior.minZoomLevel] and [MapZoomPanBehavior.maxZoomLevel] - /// properties respectively. The default values of - /// [MapZoomPanBehavior.minZoomLevel] and [MapZoomPanBehavior.maxZoomLevel] - /// are 0 and 15 respectively. However, for [MapTileLayer], - /// the [MapZoomPanBehavior.maxZoomLevel] may slightly vary depends - /// on the providers. Kindly check the respective official website of the map - /// tile providers to know about the maximum zoom level it supports. - /// - /// Toolbar with the the options for changing the visible bound for the web - /// platform will be enabled by default. However, you can use the - /// [MapZoomPanBehavior.showToolbar] property to changes its visibility. - /// - /// The procedure and the behavior are similar for both the [MapShapeLayer] - /// and [MapTileLayer]. - /// - /// ```dart - /// MapZoomPanBehavior _zoomPanBehavior; - /// - /// @override - /// void initState() { - /// _zoomPanBehavior = MapZoomPanBehavior() - /// ..zoomLevel = 4 - /// ..focalLatLng = MapLatLng(19.0759837, 72.8776559); - /// super.initState(); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar( - /// title: Text('Zoom pan'), - /// ), - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', - /// shapeDataField: 'continent', - /// ), - /// zoomPanBehavior: _zoomPanBehavior, - /// ), - /// ], - /// ), - /// ); - /// } - /// ``` - final MapZoomPanBehavior zoomPanBehavior; - - /// Called whenever zooming is happening. - /// - /// If it returns false, zooming will not happen. - /// - /// [MapZoomDetails] contains following properties. - /// * [MapZoomDetails.previousVisibleBounds] - provides the visible bounds - /// before the current zooming operation completes i.e. current visible - /// bounds. - /// * [MapZoomDetails.newVisibleBounds] - provides the new visible bounds - /// when the current zoom completes. Hence, if it returns false, - /// there will be no changes in the UI. - /// * [MapZoomDetails.previousZoomLevel] - provides the zoom level - /// before the current zooming operation completes i.e. current zoom - /// level. - /// * [MapZoomDetails.newZoomLevel] - provides the new zoom level - /// when the current zoom completes. Hence, if it returns false, there will - /// be no changes in the UI. - /// * [MapZoomDetails.globalFocalPoint] - The global focal point of the - /// pointers in contact with the screen. - /// * [MapZoomDetails.localFocalPoint] - The local focal point of the - /// pointers in contact with the screen. - final WillZoomCallback onWillZoom; - - /// Called whenever panning is happening. - /// - /// If it returns false, panning will not happen. - /// - /// [MapPanDetails] contains following properties. - /// * [MapPanDetails.previousVisibleBounds] - provides the visible bounds - /// before the current panning operation completes i.e. current visible - /// bounds. - /// * [MapPanDetails.newVisibleBounds] - provides the new visible bounds - /// when the current pan completes. Hence, if it returns false, - /// there will be no changes in the UI. - /// * [MapPanDetails.zoomLevel] - provides the current zoom level. - /// * [MapPanDetails.delta] - The difference in pixels between touch start - /// and current touch position. - /// * [MapPanDetails.globalFocalPoint] - The global focal point of the - /// pointers in contact with the screen. - /// * [MapPanDetails.localFocalPoint] - The local focal point of the - /// pointers in contact with the screen. - final WillPanCallback onWillPan; -} - -/// The shape layer in which geographical rendering is done. -/// -/// The actual geographical rendering is done here using the -/// [MapShapeLayer.delegate]. The path of the .json file which contains the -/// GeoJSON data has to be set to the [MapShapeLayerDelegate.shapeFile]. -/// -/// The [MapShapeLayerDelegate.shapeDataField] property is used to -/// refer the unique field name in the .json file to identify each shapes and -/// map with the respective data in the data source. -/// -/// By default, the value specified for the -/// [MapShapeLayerDelegate.shapeDataField] in the GeoJSON file will be used in -/// the elements like data labels, tooltip, and legend for their respective -/// shapes. -/// -/// However, it is possible to keep a data source and customize these elements -/// based on the requirement. The value of the -/// [MapShapeLayerDelegate.shapeDataField] will be used to map with the -/// respective data returned in [MapShapeLayerDelegate.primaryValueMapper] -/// from the data source. -/// -/// Once the above mapping is done, you can customize the elements using the -/// APIs like [MapShapeLayerDelegate.dataLabelMapper], -/// [MapShapeLayerDelegate.shapeColorMappers], -/// [MapShapeLayerDelegate.shapeTooltipTextMapper], etc. -/// -/// The snippet below shows how to render the basic world map using the data -/// from .json file. -/// -/// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return SfMaps( -/// layers: [ -/// MapShapeLayer( -/// delegate: MapShapeLayerDelegate( -/// shapeFile: "assets/world_map.json", -/// shapeDataField: "name", -/// ), -/// ) -/// ], -/// ); -/// } -/// ``` -/// See also: -/// * [delegate], to provide data for the elements of the [SfMaps] like data -/// labels, bubbles, tooltip, shape colors, and legend. -class MapShapeLayer extends MapLayer { - /// Creates a [MapShapeLayer]. - const MapShapeLayer({ - Key key, - @required this.delegate, - this.loadingBuilder, - this.controller, - int initialMarkersCount = 0, - MapMarkerBuilder markerBuilder, - this.shapeTooltipBuilder, - this.bubbleTooltipBuilder, - this.showDataLabels = false, - this.showBubbles = false, - this.enableShapeTooltip = false, - this.enableBubbleTooltip = false, - this.enableSelection = false, - this.color, - this.strokeColor, - this.strokeWidth, - this.palette, - this.legendSource = MapElement.none, - this.legendSettings = const MapLegendSettings(), - this.dataLabelSettings = const MapDataLabelSettings(), - this.bubbleSettings = const MapBubbleSettings(), - this.selectionSettings = const MapSelectionSettings(), - this.tooltipSettings = const MapTooltipSettings(), - this.initialSelectedIndex = -1, - MapZoomPanBehavior zoomPanBehavior, - this.onSelectionChanged, - WillZoomCallback onWillZoom, - WillPanCallback onWillPan, - }) : super( - key: key, - initialMarkersCount: initialMarkersCount, - markerBuilder: markerBuilder, - zoomPanBehavior: zoomPanBehavior, - onWillZoom: onWillZoom, - onWillPan: onWillPan, - ); - - /// The delegate that maps the data source with the shape file and provides - /// data for the elements of the [SfMaps] like data labels, bubbles, tooltip, - /// and shape colors. - /// - /// The path of the .json file which contains the - /// GeoJSON data has to be set to the [MapShapeLayerDelegate.shapeFile]. - /// - /// The [MapShapeLayerDelegate.shapeDataField] property is used to - /// refer the unique field name in the .json file to identify each shapes and - /// map with the respective data in the data source. - /// - /// By default, the value specified for the - /// [MapShapeLayerDelegate.shapeDataField] in the GeoJSON file will be used in - /// the elements like data labels, tooltip, and legend for their respective - /// shapes. - /// - /// However, it is possible to keep a data source and customize these elements - /// based on the requirement. The value of the - /// [MapShapeLayerDelegate.shapeDataField] will be used to map with the - /// respective data returned in [MapShapeLayerDelegate.primaryValueMapper] - /// from the data source. - /// - /// Once the above mapping is done, you can customize the elements using the - /// APIs like [MapShapeLayerDelegate.dataLabelMapper], - /// [MapShapeLayerDelegate.shapeColorMappers], - /// [MapShapeLayerDelegate.shapeTooltipTextMapper], etc. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// dataLabelMapper: (index) { - /// return data[index].countryCode; - /// }), - /// ) - /// ], - /// ); - /// } - /// ``` - /// See also: - /// * [MapShapeLayerDelegate.primaryValueMapper], to map the data of the data - /// source collection with the respective - /// [MapShapeLayerDelegate.shapeDataField] in .json file. - /// * [MapShapeLayerDelegate.bubbleSizeMapper], to customize the bubble size. - /// * [MapShapeLayerDelegate.dataLabelMapper], to customize the - /// data label's text. - /// * [MapShapeLayerDelegate.shapeTooltipTextMapper], to customize the - /// shape tooltip text. - /// * [MapShapeLayerDelegate.bubbleTooltipTextMapper], to customize the - /// bubble tooltip text. - /// * [MapShapeLayerDelegate.shapeColorValueMapper] and - /// [MapShapeLayerDelegate.shapeColorMappers], to customize the shape colors. - /// * [MapShapeLayerDelegate.bubbleColorValueMapper] and - /// [MapShapeLayerDelegate.bubbleColorMappers], to customize the - /// bubble colors. - final MapShapeLayerDelegate delegate; - - /// A builder that specifies the widget to display to the user while the - /// map is still loading. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Padding( - /// padding: EdgeInsets.all(15), - /// child: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', - /// shapeDataField: 'continent', - /// ), - /// loadingBuilder: (BuildContext context) { - /// return Container( - /// height: 25, - /// width: 25, - /// child: const CircularProgressIndicator( - /// strokeWidth: 3, - /// ), - /// ); - /// }, - /// ), - /// ], - /// ), - /// ), - /// ); - /// } - /// ``` - final MapLoadingBuilder loadingBuilder; - - /// Returns a widget for the shape tooltip based on the index. - /// - /// A shape tooltip displays additional information about - /// the shapes on a map. To enable tooltip for the shape, set - /// [MapShapeLayer.enableShapeTooltip] to `true`. By default, the text - /// returned in the [MapShapeLayerDelegate.shapeTooltipTextMapper] will be - /// shown in the tooltip. The [MapShapeLayer.shapeTooltipBuilder] can be used - /// to return a completely customized widget. This widget will then be wrapped - /// in the existing tooltip shape which comes with the nose at the bottom. It - /// is still possible to customize the stroke appearance using the - /// [MapTooltipSettings.strokeColor] and [MapTooltipSettings.strokeWidth]. To - /// customize the corners, use [SfMapsThemeData.tooltipBorderRadius]. - /// - /// The [MapShapeLayer.shapeTooltipBuilder] callback will be called when the - /// user interacts with the shapes. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Padding( - /// padding: EdgeInsets.all(15), - /// child: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', - /// shapeDataField: 'continent', - /// dataCount: worldMapData.length, - /// primaryValueMapper: (index) => - /// worldMapData[index].primaryKey, - /// ), - /// shapeTooltipBuilder: (BuildContext context, int index) { - /// if(index == 0) { - /// return Container( - /// child: Icon(Icons.airplanemode_inactive), - /// ); - /// } - /// else - /// { - /// return Container( - /// child: Icon(Icons.airplanemode_active), - /// ); - /// } - /// }, - /// enableShapeTooltip: true, - /// ), - /// ], - /// ), - /// ), - /// ); - /// } - /// ``` - final IndexedWidgetBuilder shapeTooltipBuilder; - - /// Returns a widget for the bubble tooltip based on the index. - /// - /// A bubble tooltip displays additional information about - /// the bubble on a map. To enable tooltip for the bubble, set - /// [MapShapeLayer.enableBubbleTooltip] to `true`. By default, the text - /// returned in the [MapShapeLayerDelegate.bubbleTooltipTextMapper] will be - /// shown in the tooltip. The [MapShapeLayer.bubbleTooltipBuilder] can be used - /// to return a completely customized widget. This widget will then be wrapped - /// in the existing tooltip shape which comes with the nose at the bottom. It - /// is still possible to customize the stroke appearance using the - /// [MapTooltipSettings.strokeColor] and [MapTooltipSettings.strokeWidth]. To - /// customize the corners, use [SfMapsThemeData.tooltipBorderRadius]. - /// - /// The [MapShapeLayer.bubbleTooltipBuilder] callback will be called when the - /// user interacts with the bubbles. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Padding( - /// padding: EdgeInsets.all(15), - /// child: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', - /// shapeDataField: 'continent', - /// dataCount: worldMapData.length, - /// primaryValueMapper: (index) => - /// worldMapData[index].primaryKey, - /// ), - /// bubbleTooltipBuilder: (BuildContext context, int index) { - /// if(index == 0) { - /// return Container( - /// child: Icon(Icons.airplanemode_inactive), - /// ); - /// } - /// else - /// { - /// return Container( - /// child: Icon(Icons.airplanemode_active), - /// ); - /// } - /// }, - /// enableBubbleTooltip: true, - /// ), - /// ], - /// ), - /// ), - /// ); - /// } - /// ``` - final IndexedWidgetBuilder bubbleTooltipBuilder; - - /// Provides option for adding, removing, deleting and updating marker - /// collection. - /// - /// You can also get the current markers count and selected shape's index from - /// this. - /// - /// ```dart - /// List data; - /// MapShapeLayerController controller; - /// Random random = Random(); - /// - /// @override - /// void initState() { - /// data = [ - /// Model(-14.235004, -51.92528), - /// Model(51.16569, 10.451526), - /// Model(-25.274398, 133.775136), - /// Model(20.593684, 78.96288), - /// Model(61.52401, 105.318756) - /// ]; - /// - /// controller = MapShapeLayerController(); - /// super.initState(); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: Container( - /// height: 350, - /// child: Padding( - /// padding: EdgeInsets.only(left: 15, right: 15), - /// child: Column( - /// children: [ - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', - /// shapeDataField: 'name', - /// ), - /// initialMarkersCount: 5, - /// markerBuilder: (BuildContext context, int index) { - /// return MapMarker( - /// latitude: data[index].latitude, - /// longitude: data[index].longitude, - /// child: Icon(Icons.add_location), - /// ); - /// }, - /// controller: controller, - /// ), - /// ], - /// ), - /// RaisedButton( - /// child: Text('Add marker'), - /// onPressed: () { - /// data.add(Model( - /// -180 + random.nextInt(360).toDouble(), - /// -55 + random.nextInt(139).toDouble())); - /// controller.insertMarker(5); - /// }, - /// ), - /// ], - /// ), - /// ), - /// ) - /// ), - /// ); - /// } - /// - /// class Model { - /// Model(this.latitude, this.longitude); - /// - /// final double latitude; - /// final double longitude; - /// } - /// ``` - final MapShapeLayerController controller; - - /// Enables or disables tooltip for the shapes. - /// - /// Defaults to `false`. - /// - /// See also: - /// * [MapTooltipSettings], to customize the tooltip. - /// * [MapShapeLayerDelegate.shapeTooltipTextMapper], for customizing the - /// tooltip text. - final bool enableShapeTooltip; - - /// Enables or disables tooltip for the bubbles. - /// - /// Defaults to false. - /// - /// See also: - /// * [MapTooltipSettings], to customize the tooltip. - /// * [MapShapeLayerDelegate.bubbleTooltipTextMapper], for customizing the - /// tooltip text. - final bool enableBubbleTooltip; - - /// Shows legend for the bubbles or shapes. - /// - /// Information provided in the legend helps to identify the data rendered in - /// the map shapes or bubbles. - /// - /// Defaults to `MapElement.none`. - /// - /// By default, legend will not be shown. - /// - /// ## Legend for shape - /// - /// [MapElement.shape] shows legend for the shapes. - /// - /// If [MapShapeLayerDelegate.shapeColorMappers] is not null, then - /// [MapColorMapper.color] and [MapColorMapper.text] will be used for the - /// legend item's icon and the legend item's text respectively. - /// - /// If [MapShapeLayerDelegate.shapeColorMappers] is null, the color returned - /// in the [MapShapeLayerDelegate.shapeColorValueMapper] will be applied to - /// the legend item's icon and the legend item's text will be taken from the - /// [MapShapeLayerDelegate.shapeDataField]. - /// - /// In a rare case, if both the [MapShapeLayerDelegate.shapeColorMappers] and - /// the [MapShapeLayerDelegate.shapeColorValueMapper] properties are null, - /// the legend item's text will be taken from the - /// [MapShapeLayerDelegate.shapeDataField] property and the legend item's - /// icon will have the default color. - /// - /// ## Legend for bubbles - /// - /// [MapElement.bubble] shows legend for the bubbles. - /// - /// If [MapShapeLayerDelegate.bubbleColorMappers] is not null, then - /// [MapColorMapper.color] and [MapColorMapper.text] will be used for the - /// legend item's icon and the legend item's text respectively. - /// - /// If [MapShapeLayerDelegate.bubbleColorMappers] is null, the color returned - /// in the [MapShapeLayerDelegate.bubbleColorValueMapper] will be applied to - /// the legend item's icon and the legend item's text will be taken from the - /// [MapShapeLayerDelegate.shapeDataField]. - /// - /// In a rare case, if both the [MapShapeLayerDelegate.bubbleColorMappers] and - /// the [MapShapeLayerDelegate.bubbleColorValueMapper] properties are null, - /// the legend item's text will be taken from the - /// [MapShapeLayerDelegate.shapeDataField] property and the legend item's - /// icon will have the default color. - /// - /// See also: - /// * [legendSettings], to enable the legend toggle interaction and customize - /// the appearance of the legend items. - final MapElement legendSource; - - /// Shows or hides the data labels. - /// - /// Defaults to `false`. - /// - /// See also: - /// * [MapDataLabelSettings], to customize the tooltip. - /// * [MapShapeLayerDelegate.dataLabelMapper], for customizing the - /// data label's text. - final bool showDataLabels; - - /// Shows or hides the bubbles. - /// - /// Defaults to `false`. - /// - /// See also: - /// * [MapBubbleSettings], to customize the appearance of the bubbles. - /// * [MapShapeLayerDelegate.bubbleSizeMapper], - /// [MapShapeLayerDelegate.bubbleColorMappers], - /// [MapShapeLayerDelegate.bubbleColorValueMapper] for customizing the - /// bubbles based on the data. - /// - /// See also: - /// * [legendSource], to enable legend for bubbles. - final bool showBubbles; - - /// Allows selecting a shape in maps. - /// - /// Defaults to `false`. - /// - /// See also: - /// * [MapShapeLayer.onSelectionChanged], for actions to be done during the - /// shape's selection change. - /// * [MapSelectionSettings], to customize the selected shape's appearance. - final bool enableSelection; - - /// Color which is used to paint the shapes. - final Color color; - - /// Color which is used to paint the stroke of the shapes. - final Color strokeColor; - - /// Sets the stroke width of the shapes. - final double strokeWidth; - - /// Paints the shape based on this list of color in a sequential order. - /// - /// If the number of shapes exceeds the length of this collections, once the - /// last color is used for a shape, color in the 0th index will be used for - /// the next shape and so on. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Padding( - /// padding: EdgeInsets.all(15), - /// child: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: 'assets/world_map.json', - /// shapeDataField: 'continent', - /// ), - /// palette: [ - /// Colors.blue[200], - /// Colors.orange[200], - /// Colors.red[200], - /// Colors.green[200], - /// Colors.purple[200], - /// Colors.lime[200] - /// ], - /// ), - /// ], - /// ), - /// ), - /// ); - /// } - /// ``` - final List palette; - - /// Customizes the appearance of the data labels. - final MapDataLabelSettings dataLabelSettings; - - /// Customizes the appearance of the bubbles. - /// - /// See also: - /// * [MapShapeLayer.showBubbles], to show the bubbles. - final MapBubbleSettings bubbleSettings; - - /// Customizes the appearance of the the legend. - /// - /// See also: - /// * [legendSource], to enable legend for shape or bubbles. - final MapLegendSettings legendSettings; - - /// Customizes the appearance of the selected shape. - /// - /// See also: - /// * [MapShapeLayer.enableSelection], for allowing shape selection. - final MapSelectionSettings selectionSettings; - - /// Customizes the bubble and shape tooltips appearance. - /// - /// See also: - /// * [MapShapeLayer.enableBubbleTooltip], for enabling tooltip for the - /// bubbles. - /// * [MapShapeLayer.enableShapeTooltip], for enabling tooltip for the - /// shapes. - final MapTooltipSettings tooltipSettings; - - /// Option to select a shape initially. - /// - /// See also: - /// * [enableSelection], to enable shape's selection. - /// * [MapSelectionSettings], to customize the selected shape's appearance. - /// * [MapShapeLayer.onSelectionChanged], for actions to be done during the - /// shape's selection change. - final int initialSelectedIndex; - - /// Called when the user is selecting a shape by tapping or clicking. - /// - /// It passes the index of the selected shape. If the selected shape is - /// tapped or clicked again, the index will be passed as -1. It means that, - /// the shape is unselected. - /// - /// This snippet shows how to use onSelectionChanged callback in [SfMaps]. - /// - /// ```dart - /// SfMaps( - /// layers: [MultiChildMapShapeLayer( - /// delegate: delegate, - /// onSelectionChanged: (int index) { - /// print('The selected region is ${data[index].labelText}'); - /// }, - /// )] - /// ) - /// - /// ``` - final ValueChanged onSelectionChanged; - - @override - Widget build(BuildContext context) { - return _MapsShapeLayer( - key: key, - delegate: delegate, - loadingBuilder: loadingBuilder, - controller: controller, - initialMarkersCount: initialMarkersCount, - markerBuilder: markerBuilder, - shapeTooltipBuilder: shapeTooltipBuilder, - bubbleTooltipBuilder: bubbleTooltipBuilder, - showDataLabels: showDataLabels, - showBubbles: showBubbles, - enableShapeTooltip: enableShapeTooltip, - enableBubbleTooltip: enableBubbleTooltip, - enableSelection: enableSelection, - color: color, - strokeColor: strokeColor, - strokeWidth: strokeWidth, - palette: palette, - legendSource: legendSource, - legendSettings: legendSettings, - dataLabelSettings: dataLabelSettings, - bubbleSettings: bubbleSettings, - selectionSettings: selectionSettings, - tooltipSettings: tooltipSettings, - initialSelectedIndex: initialSelectedIndex, - zoomPanBehavior: zoomPanBehavior, - onSelectionChanged: onSelectionChanged, - onWillZoom: onWillZoom, - onWillPan: onWillPan, - ); - } -} - -class _MapsShapeLayer extends StatefulWidget { - const _MapsShapeLayer({ - Key key, - this.delegate, - this.loadingBuilder, - this.controller, - this.initialMarkersCount, - this.markerBuilder, - this.shapeTooltipBuilder, - this.bubbleTooltipBuilder, - this.enableShapeTooltip, - this.enableBubbleTooltip, - this.enableSelection, - this.showDataLabels, - this.showBubbles, - this.color, - this.strokeColor, - this.strokeWidth, - this.palette, - this.legendSource, - this.legendSettings, - this.dataLabelSettings, - this.bubbleSettings, - this.selectionSettings, - this.tooltipSettings, - this.initialSelectedIndex, - this.zoomPanBehavior, - this.onSelectionChanged, - this.onWillZoom, - this.onWillPan, - }) : super(key: key); - - final MapShapeLayerDelegate delegate; - final MapLoadingBuilder loadingBuilder; - final MapShapeLayerController controller; - final int initialMarkersCount; - final MapMarkerBuilder markerBuilder; - final IndexedWidgetBuilder shapeTooltipBuilder; - final IndexedWidgetBuilder bubbleTooltipBuilder; - final bool enableShapeTooltip; - final bool enableBubbleTooltip; - final MapElement legendSource; - final bool showDataLabels; - final bool showBubbles; - final bool enableSelection; - final Color color; - final Color strokeColor; - final double strokeWidth; - final List palette; - final MapDataLabelSettings dataLabelSettings; - final MapLegendSettings legendSettings; - final MapBubbleSettings bubbleSettings; - final MapSelectionSettings selectionSettings; - final MapTooltipSettings tooltipSettings; - final int initialSelectedIndex; - final MapZoomPanBehavior zoomPanBehavior; - final ValueChanged onSelectionChanged; - final WillZoomCallback onWillZoom; - final WillPanCallback onWillPan; - - @override - _MapsShapeLayerState createState() => _MapsShapeLayerState(); -} - -class _MapsShapeLayerState extends State<_MapsShapeLayer> - with TickerProviderStateMixin { - bool _isShapeFileDecoded = false; - int paletteLength; - bool _shouldUpdateMapDataSource = true; - List _markers; - MapLegendSettings _legendSettings; - int _initialSelectedIndex; - SfMapsThemeData _themeData; - double minBubbleValue; - double maxBubbleValue; - _ShapeFileData shapeFileData; - - _DefaultController _defaultController; - AnimationController bubbleAnimationController; - AnimationController dataLabelAnimationController; - AnimationController selectionAnimationController; - AnimationController toggleAnimationController; - AnimationController hoverShapeAnimationController; - AnimationController hoverBubbleAnimationController; - - @override - void initState() { - super.initState(); - assert(widget.delegate != null); - shapeFileData = _ShapeFileData() - ..source = {} - ..bounds = _ShapeBounds(); - dataLabelAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 750)); - bubbleAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 500)); - selectionAnimationController = AnimationController( - vsync: this, value: 1.0, duration: const Duration(milliseconds: 200)); - toggleAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); - - hoverShapeAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); - hoverBubbleAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); - - if (widget.controller != null) { - widget.controller._markersCount = widget.initialMarkersCount; - widget.controller._selectedIndex = widget.initialSelectedIndex; - } - - _initialSelectedIndex = widget.initialSelectedIndex; - paletteLength = widget.palette != null ? widget.palette.length : 0; - - MapMarker marker; - _markers = []; - for (int i = 0; i < widget.initialMarkersCount; i++) { - marker = widget.markerBuilder(context, i); - assert(marker != null); - _markers.add(marker); - } - - widget.controller?.addListener(refreshMarkers); - - _defaultController = _DefaultController(); - widget.zoomPanBehavior?._controller = _defaultController; - } - - @override - void dispose() { - dataLabelAnimationController?.dispose(); - bubbleAnimationController?.dispose(); - selectionAnimationController?.dispose(); - toggleAnimationController?.dispose(); - hoverShapeAnimationController?.dispose(); - hoverBubbleAnimationController.dispose(); - - _markers?.clear(); - widget.controller?.removeListener(refreshMarkers); - _defaultController?.dispose(); - - shapeFileData?.reset(); - super.dispose(); - } - - @override - void didUpdateWidget(_MapsShapeLayer oldWidget) { - assert(widget.delegate != null); - _shouldUpdateMapDataSource = oldWidget.delegate != widget.delegate; - if (oldWidget.palette != widget.palette) { - paletteLength = widget.palette != null ? widget.palette.length : 0; - } - - if (oldWidget.delegate.shapeFile != widget.delegate.shapeFile) { - _isShapeFileDecoded = false; - shapeFileData?.reset(); - } - - if (oldWidget.zoomPanBehavior != widget.zoomPanBehavior) { - widget.zoomPanBehavior._controller = _defaultController; - } - - if (oldWidget.controller != widget.controller) { - widget.controller._parentBox = context.findRenderObject(); - } - - super.didUpdateWidget(oldWidget); - } - - @override - Widget build(BuildContext context) { - assert(!widget.showDataLabels || - (widget.showDataLabels && widget.delegate.shapeDataField != null)); - assert(!widget.showBubbles || - widget.showBubbles && - widget.delegate.primaryValueMapper != null && - widget.delegate.bubbleSizeMapper != null); - assert(widget.delegate.dataLabelMapper == null || - (widget.delegate.dataLabelMapper != null && widget.showDataLabels)); - assert(widget.delegate.bubbleSizeMapper == null || - (widget.delegate.bubbleSizeMapper != null && widget.showBubbles)); - assert(widget.delegate.shapeColorMappers == null || - widget.delegate.shapeColorMappers.isNotEmpty); - assert(widget.legendSource == MapElement.none || - (widget.legendSource == MapElement.shape && - widget.delegate.shapeDataField != null) || - (widget.legendSource == MapElement.bubble && widget.showBubbles)); - assert(widget.bubbleTooltipBuilder == null || - (widget.bubbleTooltipBuilder != null && widget.enableBubbleTooltip)); - assert(widget.shapeTooltipBuilder == null || - (widget.shapeTooltipBuilder != null && widget.enableShapeTooltip)); - - _updateThemeData(context); - _legendSettings = widget.legendSettings._copyWith( - textStyle: _themeData.legendTextStyle, - toggledItemColor: _themeData.toggledItemColor, - toggledItemStrokeColor: _themeData.toggledItemStrokeColor, - toggledItemStrokeWidth: _themeData.toggledItemStrokeWidth); - - return FutureBuilder<_ShapeFileData>( - future: _retrieveDataFromShapeFile(widget.delegate.shapeFile, - widget.delegate.shapeDataField, shapeFileData, _isShapeFileDecoded), - builder: (BuildContext context, AsyncSnapshot<_ShapeFileData> snapshot) { - if (snapshot.hasData && _isShapeFileDecoded) { - shapeFileData = snapshot.data; - if (_shouldUpdateMapDataSource) { - minBubbleValue = null; - maxBubbleValue = null; - shapeFileData.source.values - .forEach((_MapModel model) => model.reset()); - _bindMapsSourceIntoDataSource(); - _shouldUpdateMapDataSource = false; - } - return _actualChild; - } else { - _isShapeFileDecoded = true; - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - final Size size = _getBoxSize(constraints); - return Container( - width: size.width, - height: size.height, - alignment: Alignment.center, - child: widget.loadingBuilder?.call(context), - ); - }, - ); - } - }, - ); - } - - void _updateThemeData(BuildContext context) { - _themeData = SfMapsTheme.of(context); - _themeData = _themeData.copyWith( - layerColor: widget.color ?? _themeData.layerColor, - layerStrokeColor: widget.strokeColor ?? _themeData.layerStrokeColor, - layerStrokeWidth: widget.strokeWidth ?? _themeData.layerStrokeWidth, - shapeHoverStrokeWidth: - _themeData.shapeHoverStrokeWidth ?? _themeData.layerStrokeWidth, - legendTextStyle: widget.legendSettings.textStyle ?? - _themeData.legendTextStyle ?? - Theme.of(context).textTheme.caption.copyWith( - color: - Theme.of(context).textTheme.caption.color.withOpacity(0.87)), - bubbleColor: widget.bubbleSettings.color ?? _themeData.bubbleColor, - bubbleStrokeColor: - widget.bubbleSettings.strokeColor ?? _themeData.bubbleStrokeColor, - bubbleStrokeWidth: - widget.bubbleSettings.strokeWidth ?? _themeData.bubbleStrokeWidth, - bubbleHoverStrokeWidth: - _themeData.bubbleHoverStrokeWidth ?? _themeData.bubbleStrokeWidth, - selectionColor: - widget.selectionSettings.color ?? _themeData.selectionColor, - selectionStrokeColor: widget.selectionSettings.strokeColor ?? - _themeData.selectionStrokeColor, - selectionStrokeWidth: widget.selectionSettings.strokeWidth ?? - _themeData.selectionStrokeWidth, - tooltipTextStyle: widget.tooltipSettings.textStyle ?? - _themeData.tooltipTextStyle ?? - Theme.of(context) - .textTheme - .caption - .copyWith(color: Theme.of(context).colorScheme.surface), - tooltipColor: widget.tooltipSettings.color ?? _themeData.tooltipColor, - tooltipStrokeColor: - widget.tooltipSettings.strokeColor ?? _themeData.tooltipStrokeColor, - tooltipStrokeWidth: - widget.tooltipSettings.strokeWidth ?? _themeData.tooltipStrokeWidth, - tooltipBorderRadius: - _themeData.tooltipBorderRadius.resolve(Directionality.of(context)), - toggledItemColor: - widget.legendSettings.toggledItemColor ?? _themeData.toggledItemColor, - toggledItemStrokeColor: widget.legendSettings.toggledItemStrokeColor ?? - _themeData.toggledItemStrokeColor, - toggledItemStrokeWidth: widget.legendSettings.toggledItemStrokeWidth ?? - _themeData.toggledItemStrokeWidth, - ); - } - - Widget get _shapeLayerRenderObjectWidget { - final List children = []; - if (widget.showBubbles) { - children.add( - _MapBubble( - delegate: widget.delegate, - mapDataSource: shapeFileData.source, - bubbleSettings: widget.bubbleSettings._copyWith( - color: _themeData.bubbleColor, - strokeColor: _themeData.bubbleStrokeColor, - strokeWidth: _themeData.bubbleStrokeWidth), - legendSettings: _legendSettings, - themeData: _themeData, - defaultController: _defaultController, - state: this, - ), - ); - } - - if (widget.showDataLabels) { - children.add( - _MapDataLabel( - mapDataSource: shapeFileData.source, - settings: widget.dataLabelSettings, - effectiveTextStyle: Theme.of(context).textTheme.caption.merge( - widget.dataLabelSettings.textStyle ?? - _themeData.dataLabelTextStyle), - themeData: _themeData, - defaultController: _defaultController, - state: this, - ), - ); - } - - if (_markers != null && _markers.isNotEmpty) { - children.add( - ClipRect( - child: _MarkerContainer( - children: _markers, - defaultController: _defaultController, - state: this, - ), - ), - ); - } - - if (widget.zoomPanBehavior != null) { - children.add( - _BehaviorViewRenderObjectWidget( - defaultController: _defaultController, - zoomPanBehavior: widget.zoomPanBehavior, - ), - ); - - if (widget.zoomPanBehavior.showToolbar && kIsWeb) { - children.add( - _MapToolbar( - onWillZoom: widget.onWillZoom, - zoomPanBehavior: widget.zoomPanBehavior, - defaultController: _defaultController, - ), - ); - } - } - - if (widget.enableBubbleTooltip || widget.enableShapeTooltip) { - children.add( - _MapTooltip( - mapDelegate: widget.delegate, - defaultController: _defaultController, - enableShapeTooltip: widget.enableShapeTooltip, - enableBubbleTooltip: widget.enableBubbleTooltip, - tooltipSettings: widget.tooltipSettings, - shapeTooltipBuilder: widget.shapeTooltipBuilder, - bubbleTooltipBuilder: widget.bubbleTooltipBuilder, - themeData: _themeData, - ), - ); - } - - return _MapShapeLayerRenderObjectWidget( - key: widget.key, - children: children, - mapDataSource: shapeFileData.source, - mapDelegate: widget.delegate, - enableShapeTooltip: widget.enableShapeTooltip, - enableBubbleTooltip: widget.enableBubbleTooltip, - enableSelection: widget.enableSelection, - legendSettings: _legendSettings, - selectionSettings: widget.selectionSettings, - zoomPanBehavior: widget.zoomPanBehavior, - bubbleSettings: widget.bubbleSettings._copyWith( - color: _themeData.bubbleColor, - strokeColor: _themeData.bubbleStrokeColor, - strokeWidth: _themeData.bubbleStrokeWidth), - themeData: _themeData, - defaultController: _defaultController, - state: this, - ); - } - - Widget get _actualChild { - if (widget.legendSource != MapElement.none) { - if (_legendSettings.offset == null) { - switch (_legendSettings.position) { - case MapLegendPosition.top: - return Column( - children: [ - _legend, - _expandedShapeLayer, - ], - ); - case MapLegendPosition.bottom: - return Column( - children: [ - _expandedShapeLayer, - _legend, - ], - ); - case MapLegendPosition.left: - return Row( - children: [ - _legend, - _expandedShapeLayer, - ], - ); - case MapLegendPosition.right: - return Row( - children: [ - _expandedShapeLayer, - _legend, - ], - ); - } - } else { - return _stackedLegendAndShapeLayer; - } - } - - return _shapeLayerRenderObjectWidget; - } - - _MapLegend get _legend => _MapLegend( - dataSource: _getLegendSource() ?? shapeFileData.source, - source: widget.legendSource, - palette: widget.palette, - settings: _legendSettings, - themeData: _themeData, - defaultController: _defaultController, - toggleAnimationController: toggleAnimationController, - ); - - List _getLegendSource() { - switch (widget.legendSource) { - case MapElement.bubble: - return widget.delegate.bubbleColorMappers; - break; - case MapElement.shape: - return widget.delegate.shapeColorMappers; - break; - case MapElement.none: - break; - } - return null; - } - - Widget get _expandedShapeLayer => Expanded( - child: Stack( - alignment: Alignment.center, - children: [_shapeLayerRenderObjectWidget], - ), - ); - - /// Returns the legend and map overlapping widget. - Widget get _stackedLegendAndShapeLayer => Stack( - children: [ - _shapeLayerRenderObjectWidget, - Align( - alignment: _getActualLegendAlignment(_legendSettings.position), - // Padding widget is used to set the custom position to the legend. - child: Padding( - padding: _getActualLegendOffset(context), - child: _legend, - ), - ), - ], - ); - - /// Returns the alignment for the legend if we set the legend offset. - AlignmentGeometry _getActualLegendAlignment(MapLegendPosition position) { - switch (position) { - case MapLegendPosition.top: - return Alignment.topCenter; - break; - case MapLegendPosition.bottom: - return Alignment.bottomCenter; - break; - case MapLegendPosition.left: - return Alignment.centerLeft; - break; - case MapLegendPosition.right: - return Alignment.centerRight; - break; - } - return Alignment.topCenter; - } - - /// Returns the padding value to render the legend based on offset value. - EdgeInsetsGeometry _getActualLegendOffset(BuildContext context) { - final Offset offset = _legendSettings.offset; - final MapLegendPosition legendPosition = - _legendSettings.position ?? MapLegendPosition.top; - // Here the default alignment is center for all the positions. - // So need to handle the offset by multiplied it by 2. - switch (legendPosition) { - // Returns the insets for the offset if the legend position is top. - case MapLegendPosition.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); - break; - // Returns the insets for the offset if the legend position is left. - case MapLegendPosition.left: - return EdgeInsets.only( - top: offset.dy > 0 ? offset.dy * 2 : 0, - bottom: offset.dy < 0 ? offset.dy.abs() * 2 : 0, - left: offset.dx > 0 ? offset.dx : 0); - break; - // Returns the insets for the offset if the legend position is right. - case MapLegendPosition.right: - return EdgeInsets.only( - top: offset.dy > 0 ? offset.dy * 2 : 0, - bottom: offset.dy < 0 ? offset.dy.abs() * 2 : 0, - right: offset.dx < 0 ? offset.dx.abs() : 0); - break; - // Returns the insets for the offset if the legend position is bottom. - case MapLegendPosition.bottom: - return EdgeInsets.only( - left: offset.dx > 0 ? offset.dx * 2 : 0, - right: offset.dx < 0 ? offset.dx.abs() * 2 : 0, - bottom: offset.dy < 0 ? offset.dy.abs() : 0); - break; - } - return EdgeInsets.zero; - } - - /// Updating [modelSource] data index based on [dataMapper] - /// value and data color based on [colorValueMapper] value. - void _bindMapsSourceIntoDataSource() { - if (widget.delegate.dataCount != null && - widget.delegate.dataCount > 0 && - widget.delegate.primaryValueMapper != null) { - final bool hasShapeColorValueMapper = - widget.delegate.shapeColorValueMapper != null; - final bool hasDataLabelMapper = widget.delegate.dataLabelMapper != null; - final bool hasBubbleColorValueMapper = - widget.delegate.bubbleColorValueMapper != null; - final bool hasBubbleSizeMapper = widget.delegate.bubbleSizeMapper != null; - - for (int i = 0; i < widget.delegate.dataCount; i++) { - final _MapModel mapModel = - shapeFileData.source[widget.delegate.primaryValueMapper(i)]; - if (mapModel != null) { - mapModel.dataIndex = i; - _updateShapeColor(hasShapeColorValueMapper, i, mapModel); - if (hasDataLabelMapper) { - mapModel.dataLabelText = widget.delegate.dataLabelMapper(i); - } - - _updateBubbleColor(hasBubbleColorValueMapper, i, mapModel); - _validateBubbleSize(hasBubbleSizeMapper, i, mapModel); - if (widget.enableSelection && - mapModel.dataIndex == _initialSelectedIndex) { - mapModel.isSelected = true; - _initialSelectedIndex = -1; - } - } - } - } - } - - void _updateShapeColor( - bool hasShapeColorValueMapper, int index, _MapModel mapModel) { - if (hasShapeColorValueMapper) { - mapModel.shapeColor = _getActualColor( - widget.delegate.shapeColorValueMapper(index), - widget.delegate.shapeColorMappers, - mapModel); - } - } - - void _updateBubbleColor( - bool hasBubbleColorValueMapper, int index, _MapModel mapModel) { - if (hasBubbleColorValueMapper) { - mapModel.bubbleColor = _getActualColor( - widget.delegate.bubbleColorValueMapper(index), - widget.delegate.bubbleColorMappers, - mapModel); - } - } - - void _validateBubbleSize( - bool hasBubbleSizeMapper, int index, _MapModel mapModel) { - if (hasBubbleSizeMapper) { - mapModel.bubbleSizeValue = widget.delegate.bubbleSizeMapper(index); - if (mapModel.bubbleSizeValue != null) { - if (minBubbleValue == null) { - minBubbleValue = mapModel.bubbleSizeValue; - maxBubbleValue = mapModel.bubbleSizeValue; - } else { - minBubbleValue = min(mapModel.bubbleSizeValue, minBubbleValue); - maxBubbleValue = max(mapModel.bubbleSizeValue, maxBubbleValue); - } - } - } - } - - /// Returns color from [MapColorMapper] based on the data source value. - Color _getActualColor(Object colorValue, List colorMappers, - _MapModel mapModel) { - MapColorMapper mapper; - final int length = colorMappers != null ? colorMappers.length : 0; - // Handles equal color mapping. - if (colorValue is String) { - for (int i = 0; i < length; i++) { - mapper = colorMappers[i]; - assert(mapper.value != null && mapper.color != null); - if (mapper.value == colorValue) { - mapModel?.legendMapperIndex = i; - return mapper.color; - } - } - } - - // Handles range color mapping. - if (colorValue is num) { - for (int i = 0; i < length; i++) { - mapper = colorMappers[i]; - assert( - mapper.from != null && mapper.to != null && mapper.color != null); - if (mapper.from <= colorValue && mapper.to >= colorValue) { - mapModel?.legendMapperIndex = i; - if (mapper.minOpacity != null && mapper.maxOpacity != null) { - return mapper.color.withOpacity(lerpDouble( - mapper.minOpacity, - mapper.maxOpacity, - (colorValue - mapper.from) / (mapper.to - mapper.from))); - } - return mapper.color; - } - } - } - - return colorValue; - } - - void refreshMarkers() { - MapMarker marker; - switch (widget.controller._markerAction) { - case _MarkerAction.insert: - marker = widget.markerBuilder(context, widget.controller._index); - if (widget.controller._index < widget.controller._markersCount) { - _markers.insert(widget.controller._index, marker); - } else if (widget.controller._index == - widget.controller._markersCount) { - _markers.add(marker); - } - widget.controller._markersCount++; - break; - case _MarkerAction.removeAt: - assert(widget.controller._index < widget.controller._markersCount); - _markers.removeAt(widget.controller._index); - widget.controller._markersCount--; - break; - case _MarkerAction.replace: - for (final int index in widget.controller._replaceableIndices) { - assert(index < widget.controller._markersCount); - marker = widget.markerBuilder(context, index); - assert(marker != null); - _markers[index] = marker; - } - break; - case _MarkerAction.clear: - _markers.clear(); - widget.controller._markersCount = 0; - break; - case _MarkerAction.none: - break; - } - - widget.controller._index = -1; - - setState(() { - // Rebuilds to visually update the markers when it was updated or added. - }); - } -} - -class _MapShapeLayerRenderObjectWidget extends Stack { - _MapShapeLayerRenderObjectWidget({ - Key key, - List children, - this.mapDataSource, - this.mapDelegate, - this.enableShapeTooltip, - this.enableBubbleTooltip, - this.enableSelection, - this.legendSettings, - this.bubbleSettings, - this.selectionSettings, - this.zoomPanBehavior, - this.themeData, - this.defaultController, - this.state, - }) : super( - key: key, - children: children ?? [], - ); - - final Map mapDataSource; - final MapShapeLayerDelegate mapDelegate; - final bool enableShapeTooltip; - final bool enableBubbleTooltip; - final bool enableSelection; - final MapLegendSettings legendSettings; - final MapBubbleSettings bubbleSettings; - final MapSelectionSettings selectionSettings; - final MapZoomPanBehavior zoomPanBehavior; - final SfMapsThemeData themeData; - final _DefaultController defaultController; - final _MapsShapeLayerState state; - - @override - RenderStack createRenderObject(BuildContext context) { - return _RenderShapeLayer( - mapDataSource: mapDataSource, - mapDelegate: mapDelegate, - enableShapeTooltip: enableShapeTooltip, - enableBubbleTooltip: enableBubbleTooltip, - enableSelection: enableSelection, - legendSettings: legendSettings, - bubbleSettings: bubbleSettings, - selectionSettings: selectionSettings, - zoomPanBehavior: zoomPanBehavior, - themeData: themeData, - defaultController: defaultController, - context: context, - state: state, - ); - } - - @override - void updateRenderObject( - BuildContext context, _RenderShapeLayer renderObject) { - renderObject - ..mapDataSource = mapDataSource - ..mapDelegate = mapDelegate - ..enableShapeTooltip = enableShapeTooltip - ..enableBubbleTooltip = enableBubbleTooltip - ..enableSelection = enableSelection - ..legendSettings = legendSettings - ..bubbleSettings = bubbleSettings - ..selectionSettings = selectionSettings - ..zoomPanBehavior = zoomPanBehavior - ..themeData = themeData - ..context = context; - } -} diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart b/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart new file mode 100644 index 000000000..4ad930d35 --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart @@ -0,0 +1,4374 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:math'; +import 'dart:typed_data' show Uint8List; +import 'dart:ui'; + +import 'package:collection/collection.dart' show MapEquality; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart' show SchedulerBinding; +import 'package:flutter/services.dart' show rootBundle; +import 'package:http/http.dart' as http; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_maps/maps.dart'; + +import '../behavior/zoom_pan_behavior.dart'; +import '../controller/default_controller.dart'; +import '../controller/shape_layer_controller.dart'; +import '../elements/bubble.dart'; +import '../elements/data_label.dart'; +import '../elements/legend.dart'; +import '../elements/marker.dart'; +import '../elements/shapes.dart'; +import '../elements/toolbar.dart'; +import '../elements/tooltip.dart'; +import '../enum.dart'; +import '../layer/layer_base.dart'; +import '../layer/vector_layers.dart'; +import '../settings.dart'; +import '../utils.dart'; + +/// The source that maps the data source with the shape file and provides +/// data for the elements of the shape layer like data labels, bubbles, tooltip, +/// and shape colors. +/// +/// The source can be set as the .json file from an asset bundle, network +/// or from [Uint8List] as bytes. Use the respective constructor depends on the +/// type of the source. +/// +/// The [MapShapeSource.shapeDataField] property is used to +/// refer the unique field name in the .json file to identify each shapes and +/// map with the respective data in the data source. +/// +/// By default, the value specified for the +/// [MapShapeSource.shapeDataField] in the GeoJSON file will be used in +/// the elements like data labels, tooltip, and legend for their respective +/// shapes. +/// +/// However, it is possible to keep a data source and customize these elements +/// based on the requirement. The value of the +/// [MapShapeSource.shapeDataField] will be used to map with the +/// respective data returned in [MapShapeSource.primaryValueMapper] +/// from the data source. +/// +/// Once the above mapping is done, you can customize the elements using the +/// APIs like [MapShapeSource.dataLabelMapper], +/// [MapShapeSource.shapeColorMappers], etc. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return +/// SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// dataCount: data.length, +/// primaryValueMapper: (index) { +/// return data[index].country; +/// }, +/// dataLabelMapper: (index) { +/// return data[index].countryCode; +/// }), +/// ) +/// ], +/// ); +/// } +/// ``` +/// See also: +/// * [MapShapeSource.primaryValueMapper], to map the data of the data +/// source collection with the respective [MapShapeSource.shapeDataField] in +/// .json file. +/// * [MapShapeSource.bubbleSizeMapper], to customize the bubble size. +/// * [MapShapeSource.dataLabelMapper], to customize the +/// data label's text. +/// * [MapShapeSource.shapeColorValueMapper] and +/// [MapShapeSource.shapeColorMappers], to customize the shape colors. +/// * [MapShapeSource.bubbleColorValueMapper] and +/// [MapShapeSource.bubbleColorMappers], to customize the +/// bubble colors. +class MapShapeSource extends DiagnosticableTree { + /// Creates a layer using the .json file from an asset bundle. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Asset sample'), + /// ), + /// body: Container( + /// color: Colors.blue[100], + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/Ireland.json', + /// shapeDataField: 'name', + /// ), + /// showDataLabels: true, + /// color: Colors.orange[100], + /// ), + /// ], + /// ), + /// ), + /// ); + /// } + /// ``` + MapShapeSource.asset( + String name, { + this.shapeDataField, + this.dataCount, + this.primaryValueMapper, + this.shapeColorMappers, + this.bubbleColorMappers, + this.dataLabelMapper, + this.bubbleSizeMapper, + this.shapeColorValueMapper, + this.bubbleColorValueMapper, + }) : _mapProvider = AssetMapProvider(name), + assert(name != null), + assert(dataCount == null || dataCount > 0), + assert(primaryValueMapper == null || + (primaryValueMapper != null && dataCount != null && dataCount > 0)), + assert(shapeColorMappers == null || + (shapeColorMappers != null && + primaryValueMapper != null && + shapeColorValueMapper != null)), + assert(bubbleColorMappers == null || + (bubbleColorMappers != null && + primaryValueMapper != null && + bubbleColorValueMapper != null)); + + /// Creates a layer using the .json file from the network. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Network sample'), + /// ), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.network( + /// 'http://www.json-generator.com/api/json/get/bVqXoJvfjC?indent=2', + /// ), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + MapShapeSource.network( + String src, { + this.shapeDataField, + this.dataCount, + this.primaryValueMapper, + this.shapeColorMappers, + this.bubbleColorMappers, + this.dataLabelMapper, + this.bubbleSizeMapper, + this.shapeColorValueMapper, + this.bubbleColorValueMapper, + }) : _mapProvider = NetworkMapProvider(src), + assert(src != null), + assert(dataCount == null || dataCount > 0), + assert(primaryValueMapper == null || + (primaryValueMapper != null && dataCount != null && dataCount > 0)), + assert(shapeColorMappers == null || + (shapeColorMappers != null && + primaryValueMapper != null && + shapeColorValueMapper != null)), + assert(bubbleColorMappers == null || + (bubbleColorMappers != null && + primaryValueMapper != null && + bubbleColorValueMapper != null)); + + /// Creates a layer using the GeoJSON source as bytes from [Uint8List]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Memory sample'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.memory( + /// bytes, + /// shapeDataField: 'name', + /// dataCount: 6, + /// primaryValueMapper: (index) => dataSource[index].key, + /// dataLabelMapper: (index) => dataSource[index].dataLabel, + /// shapeColorValueMapper: (index) => dataSource[index].color, + /// ), + /// showDataLabels: true, + /// color: Colors.orange[100], + /// ), + /// ], + /// )), + /// ); + /// } + /// ``` + MapShapeSource.memory( + Uint8List bytes, { + this.shapeDataField, + this.dataCount, + this.primaryValueMapper, + this.shapeColorMappers, + this.bubbleColorMappers, + this.dataLabelMapper, + this.bubbleSizeMapper, + this.shapeColorValueMapper, + this.bubbleColorValueMapper, + }) : _mapProvider = MemoryMapProvider(bytes), + assert(bytes != null), + assert(dataCount == null || dataCount > 0), + assert(primaryValueMapper == null || + (primaryValueMapper != null && dataCount != null && dataCount > 0)), + assert(shapeColorMappers == null || + (shapeColorMappers != null && + primaryValueMapper != null && + shapeColorValueMapper != null)), + assert(bubbleColorMappers == null || + (bubbleColorMappers != null && + primaryValueMapper != null && + bubbleColorValueMapper != null)); + + /// Field name in the .json file to identify each shape. + /// + /// It is used to refer the field name in the .json file to identify + /// each shape and map that shape with the respective data in + /// the data source. + final String shapeDataField; + + /// Length of the data source. + final int dataCount; + + /// Collection of [MapColorMapper] which specifies shape's color based on the + /// data. + /// + /// It provides option to set the shape color based on the specific + /// [MapColorMapper.value] or the range of values which falls between + /// [MapColorMapper.from] and [MapColorMapper.to]. + /// + /// Based on the returned values, legend items will be rendered. The text of + /// legend item will be [MapColorMapper.text] of the [MapColorMapper]. + /// + /// The below code snippet represents how color can be applied to the shape + /// based on the [MapColorMapper.value] property of [MapColorMapper]. + /// + /// ```dart + /// List data; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: data.length, + /// primaryValueMapper: (index) { + /// return data[index].country; + /// }, + /// shapeColorValueMapper: (index) { + /// return data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) + /// ]), + /// ) + /// ], + /// ); + /// } + /// ``` + /// The below code snippet represents how color can be applied to the shape + /// based on the range between [MapColorMapper.from] and [MapColorMapper.to] + /// properties of [MapColorMapper]. + /// + /// ```dart + /// List data; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// data = [ + /// Model('India', 100, "Low"), + /// Model('United States of America', 200, "High"), + /// Model('Pakistan', 75, "Low"), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: data.length, + /// primaryValueMapper: (index) { + /// return data[index].country; + /// }, + /// shapeColorValueMapper: (index) { + /// return data[index].count; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(from: 0, to: 100, color: Colors.red), + /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) + /// ]), + /// ) + /// ], + /// ); + /// } + /// ``` + final List shapeColorMappers; + + /// Collection of [MapColorMapper] which specifies bubble's color + /// based on the data. + /// + /// It provides option to set the bubble color based on the specific + /// [MapColorMapper.value] or the range of values which falls between + /// [MapColorMapper.from] and [MapColorMapper.to]. + /// + /// The below code snippet represents how color can be applied to the bubble + /// based on the [MapColorMapper.value] property of [MapColorMapper]. + /// + /// ```dart + /// List data; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: data.length, + /// primaryValueMapper: (index) { + /// return data[index].country; + /// }, + /// bubbleColorValueMapper: (index) { + /// return data[index].usersCount; + /// }, + /// bubbleSizeMapper: (index) { + /// return data[index].usersCount; + /// }, + /// bubbleColorMappers: [ + /// MapColorMapper(from: 0, to: 100, color: Colors.red), + /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) + /// ]), + /// ) + /// ], + /// ); + /// } + /// ``` + /// The below code snippet represents how color can be applied to the bubble + /// based on the range between [MapColorMapper.from] and [MapColorMapper.to] + /// properties of [MapColorMapper]. + /// + /// ```dart + /// List data; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: data.length, + /// primaryValueMapper: (index) { + /// return data[index].country; + /// }, + /// bubbleColorValueMapper: (index) { + /// return data[index].storage; + /// }, + /// bubbleSizeMapper: (index) { + /// return data[index].usersCount; + /// }, + /// bubbleColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.yellow) + /// ]), + /// ) + /// ], + /// ); + /// } + /// ``` + final List bubbleColorMappers; + + /// Returns the the primary value for the every data in the data source + /// collection. + /// + /// This primary value will be mapped with the [shapeDataField] value in the + /// respective shape detail in the .json file. This mapping will then be used + /// in the rendering of bubbles, data labels, shape colors, tooltip + /// in their respective shape's coordinates. + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// } + /// ), + /// ) + /// ], + /// ); + /// } + /// ``` + final IndexedStringValueMapper primaryValueMapper; + + /// Returns the data label text for each shape. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// showDataLabels: true, + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }, + /// dataLabelMapper: (index) { + /// return bubbleData[index].country; + /// } + /// ), + /// ) + /// ], + /// ); + /// } + /// ``` + final IndexedStringValueMapper dataLabelMapper; + + /// Returns a value based on which bubble size will be calculated. + /// + /// The minimum and maximum size of the bubble can be customized using the + /// [MapBubbleSettings.minRadius] and [MapBubbleSettings.maxRadius]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }, + /// bubbleSizeMapper: (index) { + /// return bubbleData[index].usersCount; + /// } + /// ), + /// ) + /// ], + /// ); + /// } + /// ``` + final IndexedDoubleValueMapper bubbleSizeMapper; + + /// Returns a color or value based on which shape color will be updated. + /// + /// If this returns a color, then this color will be applied to the shape + /// straightaway. + /// + /// If it returns a value other than the color, then you must set the + /// [MapShapeSource.shapeColorMappers] property. + /// + /// The value returned from the [shapeColorValueMapper] will be used for the + /// comparison in the [MapColorMapper.value] or [MapColorMapper.from] and + /// [MapColorMapper.to]. Then, the [MapColorMapper.color] will be applied to + /// the respective shape. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }, + /// shapeColorValueMapper: (index) { + /// return bubbleData[index].country; + /// } + /// ), + /// ) + /// ], + /// ); + /// } + /// ``` + final IndexedColorValueMapper shapeColorValueMapper; + + /// Returns a color or value based on which bubble color will be updated. + /// + /// If this returns a color, then this color will be applied to the bubble + /// straightaway. + /// + /// If it returns a value other than the color, then you must set the + /// [MapShapeSource.bubbleColorMappers] property. + /// + /// The value returned from the [bubbleColorValueMapper] will be used for the + /// comparison in the [MapColorMapper.value] or [MapColorMapper.from] and + /// [MapColorMapper.to]. Then, the [MapColorMapper.color] will be applied to + /// the respective bubble. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }, + /// bubbleColorValueMapper: (index) { + /// return bubbleData[index].country; + /// }, + /// bubbleSizeMapper: (index) { + /// return bubbleData[index].usersCount; + /// } + /// ), + /// ) + /// ], + /// ); + /// } + /// ``` + final IndexedColorValueMapper bubbleColorValueMapper; + + /// Converts json file to future string based on asset, network, + /// memory and file. + final MapProvider _mapProvider; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + if (_mapProvider.shapePath != null) { + properties.add(StringProperty(null, _mapProvider.shapePath)); + } + if (_mapProvider.bytes != null) { + properties.add(StringProperty(null, 'Shape source in bytes')); + } + 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)); + } +} + +class _ShapeBounds { + _ShapeBounds( + {this.minLongitude, + this.minLatitude, + this.maxLongitude, + this.maxLatitude}); + + num minLongitude; + + num minLatitude; + + num maxLongitude; + + num maxLatitude; + + _ShapeBounds get empty => _ShapeBounds( + minLongitude: null, + minLatitude: null, + maxLongitude: null, + maxLatitude: null); +} + +class _ShapeFileData { + Map decodedJsonData; + + Map mapDataSource; + + _ShapeBounds bounds; + + MapModel initialSelectedModel; + + void reset() { + decodedJsonData?.clear(); + mapDataSource?.clear(); + bounds = bounds?.empty; + } +} + +Future<_ShapeFileData> _retrieveDataFromShapeFile( + MapProvider provider, + String shapeDataField, + _ShapeFileData shapeFileData, + bool isShapeFileDecoded, + bool isSublayer) async { + if (isShapeFileDecoded) { + return shapeFileData; + } + final String assertBundleData = await provider.loadString(); + final Map data = { + 'AssertBundleData': assertBundleData, + 'ShapeDataField': shapeDataField, + 'ShapeFileData': shapeFileData, + 'IsSublayer': isSublayer + }; + return compute(_decodeJsonData, data); +} + +_ShapeFileData _decodeJsonData(Map data) { + data['ShapeFileData'].decodedJsonData = jsonDecode(data['AssertBundleData']); + _readJsonFile(data); + return data['ShapeFileData']; +} + +void _readJsonFile(Map data) { + List polygonGeometryData; + int multipolygonGeometryLength; + Map geometry; + Map properties; + + final _ShapeFileData shapeFileData = data['ShapeFileData']; + final String shapeDataField = data['ShapeDataField']; + final bool isSublayer = data['IsSublayer']; + final bool hasFeatures = + shapeFileData.decodedJsonData.containsKey('features'); + final bool hasGeometries = + shapeFileData.decodedJsonData.containsKey('geometries'); + final String key = hasFeatures + ? 'features' + : hasGeometries + ? 'geometries' + : null; + final int jsonLength = + key.isEmpty ? 0 : shapeFileData.decodedJsonData[key].length; + if (isSublayer) { + shapeFileData.bounds = _ShapeBounds( + minLatitude: minimumLatitude, + maxLatitude: maximumLatitude, + minLongitude: minimumLongitude, + maxLongitude: maximumLongitude); + } + + for (int i = 0; i < jsonLength; i++) { + if (hasFeatures) { + final dynamic features = shapeFileData.decodedJsonData[key][i]; + geometry = features['geometry']; + properties = features['properties']; + } else if (hasGeometries) { + geometry = shapeFileData.decodedJsonData[key][i]; + } + + if (geometry['type'] == 'Polygon') { + polygonGeometryData = geometry['coordinates'][0]; + _updateMapDataSource(shapeFileData, shapeDataField, properties, + polygonGeometryData, isSublayer); + } else { + multipolygonGeometryLength = geometry['coordinates'].length; + for (int j = 0; j < multipolygonGeometryLength; j++) { + polygonGeometryData = geometry['coordinates'][j][0]; + _updateMapDataSource(shapeFileData, shapeDataField, properties, + polygonGeometryData, isSublayer); + } + } + } +} + +void _updateMapDataSource(_ShapeFileData shapeFileData, String shapeDataField, + Map properties, List points, bool isSublayer) { + final String dataPath = + properties != null ? properties[shapeDataField] : null; + shapeFileData.mapDataSource.update( + dataPath, + (MapModel model) { + model.rawPoints.add(points); + return model; + }, + ifAbsent: () { + final int dataSourceIndex = shapeFileData.mapDataSource.length; + return MapModel( + primaryKey: dataPath, + actualIndex: dataSourceIndex, + legendMapperIndex: dataSourceIndex, + rawPoints: >[points], + ); + }, + ); + if (!isSublayer) { + _updateShapeBounds(shapeFileData, points); + } +} + +void _updateShapeBounds( + _ShapeFileData shapeFileData, List coordinates) { + List data; + num longitude, latitude; + final int length = coordinates.length; + for (int i = 0; i < length; i++) { + data = coordinates[i]; + longitude = data[0]; + latitude = data[1]; + if (shapeFileData.bounds.minLongitude == null) { + shapeFileData.bounds.minLongitude = longitude; + shapeFileData.bounds.minLatitude = latitude; + shapeFileData.bounds.maxLongitude = longitude; + shapeFileData.bounds.maxLatitude = latitude; + } else { + shapeFileData.bounds.minLongitude = + min(longitude, shapeFileData.bounds.minLongitude); + shapeFileData.bounds.minLatitude = + min(latitude, shapeFileData.bounds.minLatitude); + shapeFileData.bounds.maxLongitude = + max(longitude, shapeFileData.bounds.maxLongitude); + shapeFileData.bounds.maxLatitude = + max(latitude, shapeFileData.bounds.maxLatitude); + } + } +} + +/// Provides option for adding, removing, deleting and updating marker +/// collection. +/// +/// You can also get the current markers count, selected shape's index and +/// convert pixel points to latitude and longitude using this. +/// +/// You need to set the instance of this to [MapShapeLayer.controller] as +/// shown in the below code snippet. +/// +/// ```dart +/// List data; +/// MapShapeLayerController controller; +/// Random random = Random(); +/// +/// @override +/// void initState() { +/// data = [ +/// Model(-14.235004, -51.92528), +/// Model(51.16569, 10.451526), +/// Model(-25.274398, 133.775136), +/// Model(20.593684, 78.96288), +/// Model(61.52401, 105.318756) +/// ]; +/// +/// controller = MapShapeLayerController(); +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: Center( +/// child: Container( +/// height: 350, +/// child: Padding( +/// padding: EdgeInsets.only(left: 15, right: 15), +/// child: Column( +/// children: [ +/// SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// 'assets/world_map.json', +/// shapeDataField: 'name', +/// ), +/// initialMarkersCount: 5, +/// markerBuilder: (BuildContext context, int index){ +/// return MapMarker( +/// latitude: data[index].latitude, +/// longitude: data[index].longitude, +/// child: Icon(Icons.add_location), +/// ); +/// }, +/// controller: controller, +/// ), +/// ], +/// ), +/// RaisedButton( +/// child: Text('Add marker'), +/// onPressed: () { +/// data.add(Model( +/// -180 + random.nextInt(360).toDouble(), +/// -55 + random.nextInt(139).toDouble())); +/// controller.insertMarker(5); +/// }, +/// ), +/// ], +/// ), +/// ), +/// ) +/// ), +/// ); +/// } +/// +/// class Model { +/// Model(this.latitude, this.longitude); +/// +/// final double latitude; +/// final double longitude; +/// } +/// ``` +class MapShapeLayerController extends MapLayerController { + RenderShapeLayer _parentBox; + + /// Returns the current markers count. + int get markersCount => _markersCount; + int _markersCount = 0; + + /// Converts pixel point to [MapLatLng]. + MapLatLng pixelToLatLng(Offset position) { + return getPixelToLatLng( + position, + _parentBox.size, + _parentBox.controller.shapeLayerOffset, + _parentBox.controller.shapeLayerSizeFactor); + } +} + +/// The sublayer in which geographical rendering is done. +/// +/// This sublayer can be added as a sublayer of both [MapShapeLayer] and +/// [MapTileLayer]. +/// +/// The actual geographical rendering is done here using the +/// [MapShapeLayer.source]. The source can be set as the .json file from an +/// asset bundle, network or from [Uint8List] as bytes. +/// +/// The [MapShapeSource.shapeDataField] property is used to +/// refer the unique field name in the .json file to identify each shapes and +/// map with the respective data in the data source. +/// +/// By default, the value specified for the +/// [MapShapeSource.shapeDataField] in the GeoJSON source will be used in +/// the elements like data labels, and tooltip for their respective +/// shapes. +/// +/// However, it is possible to keep a data source and customize these elements +/// based on the requirement. The value of the +/// [MapShapeSource.shapeDataField] will be used to map with the +/// respective data returned in [MapShapeSource.primaryValueMapper] +/// from the data source. +/// +/// Once the above mapping is done, you can customize the elements using the +/// APIs like [MapShapeSource.dataLabelMapper], +/// [MapShapeSource.shapeColorMappers], etc. +/// +/// The snippet below shows how to render the basic world map using the data +/// from .json file. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// ), +/// sublayer:[ +/// MapShapeSublayer( +/// source: MapShapeSource.asset( +/// "assets/africa.json", +/// shapeDataField: "name", +/// ), +/// ), +/// ] +/// ) +/// ], +/// ); +/// } +/// ``` +/// See also: +/// * [source], to provide data for the elements of this layer like data +/// labels, bubbles, tooltip, and shape colors. +class MapShapeSublayer extends MapSublayer { + /// Creates a [MapShapeSublayer]. + const MapShapeSublayer({ + Key key, + @required this.source, + this.controller, + this.initialMarkersCount = 0, + this.markerBuilder, + this.shapeTooltipBuilder, + this.bubbleTooltipBuilder, + this.markerTooltipBuilder, + this.selectedIndex = -1, + this.onSelectionChanged, + this.showDataLabels = false, + this.color, + this.strokeColor, + this.strokeWidth, + this.dataLabelSettings = const MapDataLabelSettings(), + this.bubbleSettings = const MapBubbleSettings(), + this.selectionSettings = const MapSelectionSettings(), + }); + + /// 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, + /// tooltip, and shape colors. + /// + /// The source can be set as the .json file from an asset bundle, network + /// or from [Uint8List] as bytes. + /// + /// The [MapShapeSource.shapeDataField] property is used to + /// refer the unique field name in the .json file to identify each shapes and + /// map with the respective data in the data source. + /// + /// By default, the value specified for the [MapShapeSource.shapeDataField] + /// in the GeoJSON file will be used in the elements like data labels, + /// tooltip, and legend for their respective shapes. + /// + /// However, it is possible to keep a data source and customize these elements + /// based on the requirement. The value of the + /// [MapShapeSource.shapeDataField] will be used to map with the + /// respective data returned in [MapShapeSource.primaryValueMapper] + /// from the data source. + /// + /// Once the above mapping is done, you can customize the elements using the + /// APIs like [MapShapeSource.dataLabelMapper], + /// [MapShapeSource.shapeColorMappers], etc. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// sublayers: [ + /// MapShapeSublayer( + /// source: MapShapeSource.asset( + /// 'assets/africa.json', + /// shapeDataField: 'name', + /// ), + /// color: Colors.blue.withOpacity(0.7), + /// strokeColor: Colors.blue, + /// ), + /// ], + /// ) + /// ], + /// ); + /// } + /// ``` + /// See also: + /// * [MapShapeSource.primaryValueMapper], to map the data of the data + /// source collection with the respective + /// [MapShapeSource.shapeDataField] in .json file. + /// * [MapShapeSource.bubbleSizeMapper], to customize the bubble size. + /// * [MapShapeSource.dataLabelMapper], to customize the data label's text. + /// * [MapShapeSource.shapeColorValueMapper] and + /// [MapShapeSource.shapeColorMappers], to customize the shape colors. + /// * [MapShapeSource.bubbleColorValueMapper] and + /// [MapShapeSource.bubbleColorMappers], to customize the bubble colors. + final MapShapeSource source; + + /// Option to set markers count initially. It cannot be updated dynamically. + /// + /// The [MapLayer.markerBuilder] callback will be called number of times equal + /// to the value specified in the [MapLayer.initialMarkersCount] property. + /// The default value of the of this property is null. + final int initialMarkersCount; + + /// Returns the [MapMarker] for the given index. Markers which be used to + /// denote the locations on the map. + /// + /// It is possible to use the built-in symbols or display a custom widget at + /// a specific latitude and longitude on a map. + /// + /// The [MapLayer.markerBuilder] callback will be called number of times equal + /// to the value specified in the [MapLayer.initialMarkersCount] property. + /// The default value of the of this property is null. + /// + /// For rendering the custom widget for the marker, pass the required widget + /// for child in [MapMarker] constructor. + final MapMarkerBuilder markerBuilder; + + /// Returns a widget for the shape tooltip based on the index. + /// + /// A shape tooltip displays additional information about the shapes on a map. + /// To show tooltip for the shape, return a widget in + /// [MapShapeSublayer.shapeTooltipBuilder]. This widget will then be wrapped + /// in the existing tooltip shape which comes with the nose at the bottom. + /// + /// It is possible to customize the stroke appearance using the + /// [MapTooltipSettings.strokeColor] and [MapTooltipSettings.strokeWidth]. + /// To customize the corners, use [SfMapsThemeData.tooltipBorderRadius]. + /// + /// The [MapShapeSublayer.shapeTooltipBuilder] callback will be called when + /// the user interacts with the shapes i.e., while tapping in touch devices + /// and hovering in the mouse enabled devices. + final IndexedWidgetBuilder shapeTooltipBuilder; + + /// Returns a widget for the bubble tooltip based on the index. + /// + /// A bubble tooltip displays additional information about the bubble + /// on a map. To show tooltip for the bubble, return a widget in + /// [MapShapeSublayer.bubbleTooltipBuilder]. This widget will then be wrapped + /// in the existing tooltip shape which comes with the nose at the bottom. + /// + /// It is possible to customize the stroke appearance using the + /// [MapTooltipSettings.strokeColor] and [MapTooltipSettings.strokeWidth]. + /// To customize the corners, use [SfMapsThemeData.tooltipBorderRadius]. + /// + /// The [MapShapeSublayer.bubbleTooltipBuilder] callback will be called when + /// the user interacts with the bubbles i.e., while tapping in touch devices + /// and hovering in the mouse enabled devices. + final IndexedWidgetBuilder bubbleTooltipBuilder; + + /// Returns the widget for the tooltip of the [MapMarker]. + /// + /// To show the tooltip for markers, return a customized widget in this. + /// This widget will then be wrapped in the in-built shape which comes with + /// the nose at the bottom. + /// + /// This will be called when the user interacts with the markers i.e., + /// while tapping in touch devices and hovering in the mouse enabled devices. + /// + /// See also: + /// * [MapLayer.tooltipSettings], to customize the color and stroke of the + /// tooltip. + /// * [SfMapsThemeData.tooltipBorderRadius], to customize the corners of the + /// tooltip. + final IndexedWidgetBuilder markerTooltipBuilder; + + /// Provides option for adding, removing, deleting and updating marker + /// collection. + /// + /// You can also get the current markers count from this. + final MapShapeLayerController controller; + + /// Shows or hides the data labels in the sublayer. + /// + /// Defaults to `false`. + final bool showDataLabels; + + /// Color which is used to paint the sublayer shapes. + final Color color; + + /// Color which is used to paint the stroke of the sublayer shapes. + final Color strokeColor; + + /// Sets the stroke width of the sublayer shapes. + final double strokeWidth; + + /// Customizes the appearance of the data labels. + final MapDataLabelSettings dataLabelSettings; + + /// Customizes the appearance of the bubbles. + final MapBubbleSettings bubbleSettings; + + /// Customizes the appearance of the selected shape. + final MapSelectionSettings selectionSettings; + + /// Selects the shape in the given index. + /// + /// The map passes the selected index to the [onSelectionChanged] callback but + /// does not actually change this value until the parent widget rebuilds the + /// maps with the new value. + /// + /// To unselect a shape, set -1 to the [MapShapeSublayer.selectedIndex]. + /// + /// Must not be null. Defaults to -1. + /// + /// See also: + /// * [MapSelectionSettings], to customize the selected shape's appearance. + /// * [MapShapeSublayer.onSelectionChanged], for getting the current tapped + /// shape index. + final int selectedIndex; + + /// Called when the user tapped or clicked on a shape. + /// + /// The map passes the selected index to the [onSelectionChanged] callback but + /// does not actually change this value until the parent widget rebuilds the + /// maps with the new value. + final ValueChanged onSelectionChanged; + + @override + Widget build(BuildContext context) { + return _MapsShapeLayer( + key: key, + source: source, + controller: controller, + initialMarkersCount: initialMarkersCount, + markerBuilder: markerBuilder, + shapeTooltipBuilder: shapeTooltipBuilder, + bubbleTooltipBuilder: bubbleTooltipBuilder, + markerTooltipBuilder: markerTooltipBuilder, + showDataLabels: showDataLabels, + color: color, + strokeColor: strokeColor, + strokeWidth: strokeWidth, + dataLabelSettings: dataLabelSettings, + bubbleSettings: bubbleSettings, + selectionSettings: selectionSettings, + tooltipSettings: const MapTooltipSettings(), + selectedIndex: selectedIndex, + onSelectionChanged: onSelectionChanged, + sublayer: this, + ); + } +} + +/// The shape layer in which geographical rendering is done. +/// +/// The actual geographical rendering is done here using the +/// [MapShapeLayer.source]. The source can be set as the .json file from an +/// asset bundle, network or from [Uint8List] as bytes. +/// +/// The [MapShapeSource.shapeDataField] property is used to +/// refer the unique field name in the .json file to identify each shapes and +/// map with the respective data in the data source. +/// +/// By default, the value specified for the +/// [MapShapeSource.shapeDataField] in the GeoJSON file will be used in +/// the elements like data labels, tooltip, and legend for their respective +/// shapes. +/// +/// However, it is possible to keep a data source and customize these elements +/// based on the requirement. The value of the +/// [MapShapeSource.shapeDataField] will be used to map with the +/// respective data returned in [MapShapeSource.primaryValueMapper] +/// from the data source. +/// +/// Once the above mapping is done, you can customize the elements using the +/// APIs like [MapShapeSource.dataLabelMapper], +/// [MapShapeSource.shapeColorMappers], etc. +/// +/// The snippet below shows how to render the basic world map using the data +/// from .json file. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// ), +/// ) +/// ], +/// ); +/// } +/// ``` +/// See also: +/// * [source], to provide data for the elements of the [SfMaps] like data +/// labels, bubbles, tooltip, shape colors, and legend. +class MapShapeLayer extends MapLayer { + /// Creates a [MapShapeLayer]. + const MapShapeLayer({ + Key key, + @required this.source, + this.loadingBuilder, + this.controller, + List sublayers, + int initialMarkersCount = 0, + MapMarkerBuilder markerBuilder, + this.shapeTooltipBuilder, + this.bubbleTooltipBuilder, + IndexedWidgetBuilder markerTooltipBuilder, + this.showDataLabels = false, + this.color, + this.strokeColor, + this.strokeWidth, + this.legend, + this.dataLabelSettings = const MapDataLabelSettings(), + this.bubbleSettings = const MapBubbleSettings(), + this.selectionSettings = const MapSelectionSettings(), + MapTooltipSettings tooltipSettings = const MapTooltipSettings(), + this.selectedIndex = -1, + MapZoomPanBehavior zoomPanBehavior, + this.onSelectionChanged, + WillZoomCallback onWillZoom, + WillPanCallback onWillPan, + }) : super( + key: key, + 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, + /// tooltip, and shape colors. + /// + /// The source can be set as the .json file from an asset bundle, network + /// or from [Uint8List] as bytes. + /// + /// The [MapShapeSource.shapeDataField] property is used to + /// refer the unique field name in the .json file to identify each shapes and + /// map with the respective data in the data source. + /// + /// By default, the value specified for the + /// [MapShapeSource.shapeDataField] in the GeoJSON file will be used in + /// the elements like data labels, tooltip, and legend for their respective + /// shapes. + /// + /// However, it is possible to keep a data source and customize these elements + /// based on the requirement. The value of the + /// [MapShapeSource.shapeDataField] will be used to map with the + /// respective data returned in [MapShapeSource.primaryValueMapper] + /// from the data source. + /// + /// Once the above mapping is done, you can customize the elements using the + /// APIs like [MapShapeSource.dataLabelMapper], + /// [MapShapeSource.shapeColorMappers], etc. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: data.length, + /// primaryValueMapper: (index) { + /// return data[index].country; + /// }, + /// dataLabelMapper: (index) { + /// return data[index].countryCode; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// See also: + /// * [MapShapeSource.primaryValueMapper], to map the data of the data + /// source collection with the respective + /// [MapShapeSource.shapeDataField] in .json file. + /// * [MapShapeSource.bubbleSizeMapper], to customize the bubble size. + /// * [MapShapeSource.dataLabelMapper], to customize the + /// data label's text. + /// * [MapShapeSource.shapeColorValueMapper] and + /// [MapShapeSource.shapeColorMappers], to customize the shape colors. + /// * [MapShapeSource.bubbleColorValueMapper] and + /// [MapShapeSource.bubbleColorMappers], to customize the + /// bubble colors. + final MapShapeSource source; + + /// A builder that specifies the widget to display to the user while the + /// map is still loading. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Padding( + /// padding: EdgeInsets.all(15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ), + /// loadingBuilder: (BuildContext context) { + /// return Container( + /// height: 25, + /// width: 25, + /// child: const CircularProgressIndicator( + /// strokeWidth: 3, + /// ), + /// ); + /// }, + /// ), + /// ], + /// ), + /// ), + /// ); + /// } + /// ``` + final MapLoadingBuilder loadingBuilder; + + /// Returns a widget for the shape tooltip based on the index. + /// + /// A shape tooltip displays additional information about + /// the shapes on a map. To show tooltip for the shape return a widget in + /// [MapShapeLayer.shapeTooltipBuilder]. This widget will + /// then be wrapped in the existing tooltip shape which comes with the nose at + /// the bottom. It is still possible to customize the stroke appearance using + /// the [MapTooltipSettings.strokeColor] and [MapTooltipSettings.strokeWidth]. + /// + /// To customize the corners, use [SfMapsThemeData.tooltipBorderRadius]. + /// + /// The will be called when the user interacts with the shapes i.e., while + /// tapping in touch devices and hovering in the mouse enabled devices. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Padding( + /// padding: EdgeInsets.all(15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// dataCount: worldMapData.length, + /// primaryValueMapper: (index) => + /// worldMapData[index].primaryKey, + /// ), + /// shapeTooltipBuilder: (BuildContext context, int index) { + /// if(index == 0) { + /// return Container( + /// child: Icon(Icons.airplanemode_inactive), + /// ); + /// } + /// else + /// { + /// return Container( + /// child: Icon(Icons.airplanemode_active), + /// ); + /// } + /// }, + /// ), + /// ], + /// ), + /// ), + /// ); + /// } + /// ``` + final IndexedWidgetBuilder shapeTooltipBuilder; + + /// Returns a widget for the bubble tooltip based on the index. + /// + /// A bubble tooltip displays additional information about the bubble on a + /// map. To show tooltip for the bubble, return a widget in + /// [MapShapeLayer.bubbleTooltipBuilder]. This widget will then be wrapped in + /// the existing tooltip shape which comes with the nose at the bottom. It is + /// still possible to customize the stroke appearance using the + /// [MapTooltipSettings.strokeColor] and [MapTooltipSettings.strokeWidth]. + /// + /// To customize the corners, use [SfMapsThemeData.tooltipBorderRadius]. + /// + /// The will be called when the user interacts with the bubbles i.e., while + /// tapping in touch devices and hovering in the mouse enabled devices. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Padding( + /// padding: EdgeInsets.all(15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// dataCount: worldMapData.length, + /// primaryValueMapper: (index) => + /// worldMapData[index].primaryKey, + /// ), + /// bubbleTooltipBuilder: (BuildContext context, int index) { + /// if(index == 0) { + /// return Container( + /// child: Icon(Icons.airplanemode_inactive), + /// ); + /// } + /// else + /// { + /// return Container( + /// child: Icon(Icons.airplanemode_active), + /// ); + /// } + /// }, + /// ), + /// ], + /// ), + /// ), + /// ); + /// } + /// ``` + final IndexedWidgetBuilder bubbleTooltipBuilder; + + /// Provides option for adding, removing, deleting and updating marker + /// collection. + /// + /// You can also get the current markers count and selected shape's index from + /// this. + /// + /// ```dart + /// List data; + /// MapShapeLayerController controller; + /// Random random = Random(); + /// + /// @override + /// void initState() { + /// data = [ + /// Model(-14.235004, -51.92528), + /// Model(51.16569, 10.451526), + /// Model(-25.274398, 133.775136), + /// Model(20.593684, 78.96288), + /// Model(61.52401, 105.318756) + /// ]; + /// + /// controller = MapShapeLayerController(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: Column( + /// children: [ + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// ), + /// initialMarkersCount: 5, + /// markerBuilder: (BuildContext context, int index) { + /// return MapMarker( + /// latitude: data[index].latitude, + /// longitude: data[index].longitude, + /// child: Icon(Icons.add_location), + /// ); + /// }, + /// controller: controller, + /// ), + /// ], + /// ), + /// RaisedButton( + /// child: Text('Add marker'), + /// onPressed: () { + /// data.add(Model( + /// -180 + random.nextInt(360).toDouble(), + /// -55 + random.nextInt(139).toDouble())); + /// controller.insertMarker(5); + /// }, + /// ), + /// ], + /// ), + /// ), + /// ) + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.latitude, this.longitude); + /// + /// final double latitude; + /// final double longitude; + /// } + /// ``` + final MapShapeLayerController controller; + + /// Shows or hides the data labels. + /// + /// Defaults to `false`. + /// + /// See also: + /// * [MapDataLabelSettings], to customize the tooltip. + /// * [MapShapeSource.dataLabelMapper], for customizing the + /// data label's text. + final bool showDataLabels; + + /// Color which is used to paint the shapes. + final Color color; + + /// Color which is used to paint the stroke of the shapes. + final Color strokeColor; + + /// Sets the stroke width of the shapes. + final double strokeWidth; + + /// Customizes the appearance of the data labels. + final MapDataLabelSettings dataLabelSettings; + + /// Customizes the appearance of the bubbles. + /// + /// See also: + /// * [MapShapeLayer.bubbleSizeMapper], to show the bubbles. + final MapBubbleSettings bubbleSettings; + + /// Shows legend for the bubbles or shapes. + /// + /// Information provided in the legend helps to identify the data rendered in + /// the map shapes or bubbles. + /// + /// Defaults to `null`. + /// + /// By default, legend will not be shown. + /// + /// ## Legend for shape + /// + /// Set [MapLegend.source] to [MapElement.shape] to show legend for shapes. + /// + /// If [MapShapeSource.shapeColorMappers] is not null, then + /// [MapColorMapper.color] and [MapColorMapper.text] will be used for the + /// legend item's icon and the legend item's text respectively. + /// + /// If [MapShapeSource.shapeColorMappers] is null, the color returned + /// in the [MapShapeSource.shapeColorValueMapper] will be applied to + /// the legend item's icon and the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField]. + /// + /// In a rare case, if both the [MapShapeSource.shapeColorMappers] and + /// the [MapShapeSource.shapeColorValueMapper] properties are null, + /// the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField] property and the legend item's + /// icon will have the default color. + /// + /// ## Legend for bubbles + /// + /// Set [MapLegend.source] to [MapElement.bubble] to show legend for bubbles. + /// + /// If [MapShapeSource.bubbleColorMappers] is not null, then + /// [MapColorMapper.color] and [MapColorMapper.text] will be used for the + /// legend item's icon and the legend item's text respectively. + /// + /// If [MapShapeSource.bubbleColorMappers] is null, the color returned + /// in the [MapShapeSource.bubbleColorValueMapper] will be applied to + /// the legend item's icon and the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField]. + /// + /// If both the [MapShapeSource.bubbleColorMappers] and + /// the [MapShapeSource.bubbleColorValueMapper] properties are null, + /// the legend item's text will be taken from the + /// [MapShapeSource.shapeDataField] property and the legend item's + /// icon will have the default color. + /// + /// See also: + /// * [MapLegend.source], to enable legend for shape or bubbles. + final MapLegend legend; + + /// Customizes the appearance of the selected shape. + /// + /// See also: + /// * [MapShapeLayer.selectedIndex], for selecting or unselecting a shape. + /// * [MapShapeLayer.onSelectionChanged], passes the current tapped shape + /// index. + final MapSelectionSettings selectionSettings; + + /// Selects the shape in the given index. + /// + /// The map passes the selected index to the [onSelectionChanged] callback but + /// does not actually change this value until the parent widget rebuilds the + /// maps with the new value. + /// + /// To unselect a shape, set -1 to the [MapShapeLayer.selectedIndex]. + /// + /// Must not be null. Defaults to -1. + /// + /// See also: + /// * [MapSelectionSettings], to customize the selected shape's appearance. + /// * [MapShapeLayer.onSelectionChanged], for getting the current tapped + /// shape index. + final int selectedIndex; + + /// Called when the user tapped or clicked on a shape. + /// + /// The map passes the selected index to the [onSelectionChanged] callback but + /// does not actually change this value until the parent widget rebuilds the + /// maps with the new value. + /// + /// This snippet shows how to use onSelectionChanged callback in [SfMaps]. + /// + /// ```dart + /// int _selectedIndex = -1; + /// + /// SfMaps( + /// layers: [MultiChildMapShapeLayer( + /// source: source, + /// selectedIndex: _selectedIndex, + /// onSelectionChanged: (int index) { + /// setState(() { + /// _selectedIndex = (_selectedIndex == index) ? -1 : index; + /// }); + /// }, + /// )] + /// ) + /// + /// ``` + final ValueChanged onSelectionChanged; + + @override + Widget build(BuildContext context) { + return _MapsShapeLayer( + key: key, + source: source, + loadingBuilder: loadingBuilder, + controller: controller, + sublayers: sublayers, + initialMarkersCount: initialMarkersCount, + markerBuilder: markerBuilder, + shapeTooltipBuilder: shapeTooltipBuilder, + bubbleTooltipBuilder: bubbleTooltipBuilder, + markerTooltipBuilder: markerTooltipBuilder, + showDataLabels: showDataLabels, + color: color, + strokeColor: strokeColor, + strokeWidth: strokeWidth, + legend: legend, + dataLabelSettings: dataLabelSettings, + bubbleSettings: bubbleSettings, + selectionSettings: selectionSettings, + tooltipSettings: tooltipSettings, + selectedIndex: selectedIndex, + zoomPanBehavior: zoomPanBehavior, + onSelectionChanged: onSelectionChanged, + onWillZoom: onWillZoom, + onWillPan: onWillPan, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(source.toDiagnosticsNode(name: 'source')); + properties.add(ObjectFlagProperty.has( + 'loadingBuilder', loadingBuilder)); + if (controller != null) { + properties.add(IntProperty('markersCount', controller.markersCount)); + } else { + properties.add(IntProperty('markersCount', initialMarkersCount)); + } + properties.add(ObjectFlagProperty.has( + 'markerBuilder', markerBuilder)); + properties.add(ObjectFlagProperty.has( + 'shapeTooltip', shapeTooltipBuilder)); + properties.add(ObjectFlagProperty.has( + 'bubbleTooltip', bubbleTooltipBuilder)); + properties.add(FlagProperty('showDataLabels', + value: showDataLabels, + ifTrue: 'Data labels are showing', + ifFalse: 'Data labels are not showing', + showName: false)); + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + if (strokeColor != null) { + properties.add(ColorProperty('strokeColor', strokeColor)); + } + + if (strokeColor != null) { + properties.add(DoubleProperty('strokeWidth', strokeWidth)); + } + + properties.add(IntProperty('selectedIndex', selectedIndex)); + 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(tooltipSettings.toDiagnosticsNode(name: 'tooltipSettings')); + if (zoomPanBehavior != null) { + properties + .add(zoomPanBehavior.toDiagnosticsNode(name: 'zoomPanBehavior')); + } + properties.add( + ObjectFlagProperty.has('onWillZoom', onWillZoom)); + properties + .add(ObjectFlagProperty.has('onWillPan', onWillPan)); + } +} + +class _MapsShapeLayer extends StatefulWidget { + const _MapsShapeLayer({ + Key key, + this.source, + this.loadingBuilder, + this.controller, + this.sublayers, + this.initialMarkersCount, + this.markerBuilder, + this.shapeTooltipBuilder, + this.bubbleTooltipBuilder, + this.markerTooltipBuilder, + this.showDataLabels, + this.color, + this.strokeColor, + this.strokeWidth, + this.legend, + this.dataLabelSettings, + this.bubbleSettings, + this.selectionSettings, + this.tooltipSettings, + this.selectedIndex, + this.zoomPanBehavior, + this.onSelectionChanged, + this.onWillZoom, + this.onWillPan, + this.sublayer, + }) : super(key: key); + + final MapShapeSource source; + final MapLoadingBuilder loadingBuilder; + final MapShapeLayerController controller; + final List sublayers; + final int initialMarkersCount; + final MapMarkerBuilder markerBuilder; + final IndexedWidgetBuilder shapeTooltipBuilder; + final IndexedWidgetBuilder bubbleTooltipBuilder; + final IndexedWidgetBuilder markerTooltipBuilder; + final bool showDataLabels; + final Color color; + final Color strokeColor; + final double strokeWidth; + final MapDataLabelSettings dataLabelSettings; + final MapLegend legend; + final MapBubbleSettings bubbleSettings; + final MapSelectionSettings selectionSettings; + final MapTooltipSettings tooltipSettings; + final int selectedIndex; + final MapZoomPanBehavior zoomPanBehavior; + final ValueChanged onSelectionChanged; + final WillZoomCallback onWillZoom; + final WillPanCallback onWillPan; + final MapShapeSublayer sublayer; + + @override + _MapsShapeLayerState createState() => _MapsShapeLayerState(); +} + +class _MapsShapeLayerState extends State<_MapsShapeLayer> + with TickerProviderStateMixin { + GlobalKey bubbleKey; + GlobalKey tooltipKey; + + List _markers; + MapLegend _legendConfiguration; + MapLayerLegend _legendWidget; + _ShapeFileData shapeFileData; + SfMapsThemeData _mapsThemeData; + + double minBubbleValue; + double maxBubbleValue; + + bool _isShapeFileDecoded = false; + bool _shouldUpdateMapDataSource = true; + bool isDesktop = false; + bool _hasSublayer = false; + bool isSublayer = false; + + MapController controller; + AnimationController toggleAnimationController; + AnimationController _hoverBubbleAnimationController; + AnimationController bubbleAnimationController; + AnimationController dataLabelAnimationController; + AnimationController hoverShapeAnimationController; + AnimationController selectionAnimationController; + AnimationController zoomLevelAnimationController; + AnimationController focalLatLngAnimationController; + + @override + void initState() { + super.initState(); + assert(widget.source != null); + bubbleKey = GlobalKey(); + tooltipKey = GlobalKey(); + shapeFileData = _ShapeFileData() + ..mapDataSource = {} + ..bounds = _ShapeBounds(); + dataLabelAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 750)); + bubbleAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 500)); + toggleAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 250)); + _hoverBubbleAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 250)); + hoverShapeAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 250)); + selectionAnimationController = AnimationController( + vsync: this, value: 1.0, duration: const Duration(milliseconds: 200)); + zoomLevelAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 650)); + focalLatLngAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 650)); + + if (widget.controller != null) { + widget.controller._markersCount = widget.initialMarkersCount; + } + + _hasSublayer = widget.sublayers != null && widget.sublayers.isNotEmpty; + MapMarker marker; + _markers = []; + for (int i = 0; i < widget.initialMarkersCount; i++) { + marker = widget.markerBuilder(context, i); + assert(marker != null); + _markers.add(marker); + } + + widget.controller?.addListener(refreshMarkers); + + isSublayer = widget.sublayer != null; + // For sublayer, we will use parent's map controller. + if (!isSublayer) { + controller = MapController()..tooltipKey = tooltipKey; + } + } + + @override + void dispose() { + dataLabelAnimationController?.dispose(); + bubbleAnimationController?.dispose(); + selectionAnimationController?.dispose(); + toggleAnimationController?.dispose(); + hoverShapeAnimationController?.dispose(); + _hoverBubbleAnimationController.dispose(); + zoomLevelAnimationController?.dispose(); + focalLatLngAnimationController?.dispose(); + + _markers?.clear(); + widget.controller?.removeListener(refreshMarkers); + if (!isSublayer) { + controller?.dispose(); + } + + shapeFileData?.reset(); + super.dispose(); + } + + @override + void didUpdateWidget(_MapsShapeLayer oldWidget) { + assert(widget.source != null); + _shouldUpdateMapDataSource = oldWidget.source != widget.source; + _hasSublayer = widget.sublayers != null && widget.sublayers.isNotEmpty; + isSublayer = widget.sublayer != null; + + if (oldWidget.source._mapProvider != widget.source._mapProvider) { + _isShapeFileDecoded = false; + shapeFileData?.reset(); + } + + if (oldWidget.controller != widget.controller) { + widget.controller._parentBox = context.findRenderObject(); + } + + if (_shouldUpdateMapDataSource && !isSublayer) { + controller.visibleFocalLatLng = null; + } + + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + assert(!widget.showDataLabels || + (widget.showDataLabels && widget.source.shapeDataField != null)); + assert(widget.source.bubbleSizeMapper == null || + widget.source.bubbleSizeMapper != null && + widget.source.primaryValueMapper != null); + assert(widget.source.dataLabelMapper == null || + (widget.source.dataLabelMapper != null && widget.showDataLabels)); + assert(widget.source.shapeColorMappers == null || + widget.source.shapeColorMappers.isNotEmpty); + assert(widget.selectedIndex != null); + + final ThemeData themeData = Theme.of(context); + _mapsThemeData = SfMapsTheme.of(context); + if (widget.legend != null) { + _legendConfiguration = widget.legend.copyWith( + textStyle: themeData.textTheme.caption + .copyWith( + color: themeData.textTheme.caption.color.withOpacity(0.87)) + .merge(widget.legend.textStyle ?? _mapsThemeData.legendTextStyle), + toggledItemColor: _mapsThemeData.toggledItemColor, + toggledItemStrokeColor: _mapsThemeData.toggledItemStrokeColor, + toggledItemStrokeWidth: _mapsThemeData.toggledItemStrokeWidth); + _legendWidget = MapLayerLegend(legend: _legendConfiguration); + } else { + _legendConfiguration = null; + } + + isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows; + _updateThemeData(context); + return FutureBuilder<_ShapeFileData>( + future: _retrieveDataFromShapeFile( + widget.source._mapProvider, + widget.source.shapeDataField, + shapeFileData, + _isShapeFileDecoded, + isSublayer), + builder: (BuildContext context, AsyncSnapshot<_ShapeFileData> snapshot) { + if (snapshot.hasData && _isShapeFileDecoded) { + shapeFileData = snapshot.data; + if (_shouldUpdateMapDataSource) { + minBubbleValue = null; + maxBubbleValue = null; + if (shapeFileData.mapDataSource != null) { + shapeFileData.mapDataSource.values + .forEach((MapModel model) => model.reset()); + } + _bindMapsSourceIntoDataSource(); + _shouldUpdateMapDataSource = false; + } + return _actualChild; + } else { + _isShapeFileDecoded = true; + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final Size size = getBoxSize(constraints); + return Container( + width: size.width, + height: size.height, + alignment: Alignment.center, + child: widget.loadingBuilder?.call(context), + ); + }, + ); + } + }, + ); + } + + void _updateThemeData(BuildContext context) { + final bool isLightTheme = _mapsThemeData.brightness == Brightness.light; + _mapsThemeData = _mapsThemeData.copyWith( + layerColor: widget.color ?? + (isSublayer + ? (isLightTheme + ? const Color.fromRGBO(198, 198, 198, 1) + : const Color.fromRGBO(71, 71, 71, 1)) + : _mapsThemeData.layerColor), + layerStrokeColor: widget.strokeColor ?? + (isSublayer + ? (isLightTheme + ? const Color.fromRGBO(145, 145, 145, 1) + : const Color.fromRGBO(133, 133, 133, 1)) + : _mapsThemeData.layerStrokeColor), + layerStrokeWidth: widget.strokeWidth ?? + (isSublayer + ? (isLightTheme ? 0.5 : 0.25) + : _mapsThemeData.layerStrokeWidth), + shapeHoverStrokeWidth: _mapsThemeData.shapeHoverStrokeWidth ?? + _mapsThemeData.layerStrokeWidth, + legendTextStyle: _legendWidget?.textStyle, + bubbleColor: widget.bubbleSettings.color ?? _mapsThemeData.bubbleColor, + bubbleStrokeColor: + widget.bubbleSettings.strokeColor ?? _mapsThemeData.bubbleStrokeColor, + bubbleStrokeWidth: + widget.bubbleSettings.strokeWidth ?? _mapsThemeData.bubbleStrokeWidth, + bubbleHoverStrokeWidth: _mapsThemeData.bubbleHoverStrokeWidth ?? + _mapsThemeData.bubbleStrokeWidth, + selectionColor: + widget.selectionSettings.color ?? _mapsThemeData.selectionColor, + selectionStrokeColor: widget.selectionSettings.strokeColor ?? + _mapsThemeData.selectionStrokeColor, + selectionStrokeWidth: widget.selectionSettings.strokeWidth ?? + _mapsThemeData.selectionStrokeWidth, + tooltipColor: widget.tooltipSettings.color ?? _mapsThemeData.tooltipColor, + tooltipStrokeColor: widget.tooltipSettings.strokeColor ?? + _mapsThemeData.tooltipStrokeColor, + tooltipStrokeWidth: widget.tooltipSettings.strokeWidth ?? + _mapsThemeData.tooltipStrokeWidth, + tooltipBorderRadius: _mapsThemeData.tooltipBorderRadius + .resolve(Directionality.of(context)), + toggledItemColor: _legendWidget?.toggledItemColor, + toggledItemStrokeColor: _legendWidget?.toggledItemStrokeColor, + toggledItemStrokeWidth: _legendWidget?.toggledItemStrokeWidth, + ); + } + + bool _hasTooltipBuilder() { + if (isSublayer) { + return false; + } + + if (widget.shapeTooltipBuilder != null || + widget.bubbleTooltipBuilder != null || + widget.markerTooltipBuilder != null) { + return true; + } else if (_hasSublayer) { + final Iterator iterator = widget.sublayers.iterator; + while (iterator.moveNext()) { + final MapSublayer sublayer = iterator.current; + if ((sublayer is MapShapeSublayer && + (sublayer.shapeTooltipBuilder != null || + sublayer.bubbleTooltipBuilder != null || + sublayer.markerTooltipBuilder != null)) || + (sublayer is MapVectorLayer && sublayer.tooltipBuilder != null)) { + return true; + } + } + } + return false; + } + + Widget get _shapeLayerRenderObjectWidget { + final List children = []; + if (widget.source.bubbleSizeMapper != null) { + children.add( + MapBubble( + key: bubbleKey, + source: widget.source, + mapDataSource: shapeFileData.mapDataSource, + bubbleSettings: widget.bubbleSettings.copyWith( + color: _mapsThemeData.bubbleColor, + strokeColor: _mapsThemeData.bubbleStrokeColor, + strokeWidth: _mapsThemeData.bubbleStrokeWidth), + legend: _legendWidget, + showDataLabels: widget.showDataLabels, + themeData: _mapsThemeData, + controller: controller, + bubbleAnimationController: bubbleAnimationController, + dataLabelAnimationController: dataLabelAnimationController, + toggleAnimationController: toggleAnimationController, + hoverBubbleAnimationController: _hoverBubbleAnimationController, + ), + ); + } + + if (widget.showDataLabels) { + children.add( + MapDataLabel( + source: widget.source, + mapDataSource: shapeFileData.mapDataSource, + settings: widget.dataLabelSettings, + effectiveTextStyle: Theme.of(context).textTheme.caption.merge( + widget.dataLabelSettings.textStyle ?? + _mapsThemeData.dataLabelTextStyle), + themeData: _mapsThemeData, + controller: controller, + dataLabelAnimationController: dataLabelAnimationController, + ), + ); + } + + if (_hasSublayer) { + children.add( + ClipRect( + child: SublayerContainer( + controller: controller, + tooltipKey: tooltipKey, + children: widget.sublayers, + ), + ), + ); + } + + if (_markers != null && _markers.isNotEmpty) { + children.add( + ClipRect( + child: ShapeLayerMarkerContainer( + tooltipKey: tooltipKey, + markerTooltipBuilder: widget.markerTooltipBuilder, + children: _markers, + controller: controller, + sublayer: widget.sublayer, + ), + ), + ); + } + + if (widget.zoomPanBehavior != null) { + children.add( + BehaviorViewRenderObjectWidget( + controller: controller, + zoomPanBehavior: widget.zoomPanBehavior, + ), + ); + + if (widget.zoomPanBehavior.showToolbar && isDesktop) { + children.add( + MapToolbar( + onWillZoom: widget.onWillZoom, + zoomPanBehavior: widget.zoomPanBehavior, + controller: controller, + ), + ); + } + } + + if (_hasTooltipBuilder()) { + children.add( + MapTooltip( + key: tooltipKey, + mapSource: widget.source, + controller: controller, + sublayers: widget.sublayers, + tooltipSettings: widget.tooltipSettings, + shapeTooltipBuilder: widget.shapeTooltipBuilder, + bubbleTooltipBuilder: widget.bubbleTooltipBuilder, + markerTooltipBuilder: widget.markerTooltipBuilder, + themeData: _mapsThemeData, + ), + ); + } + + return _MapShapeLayerRenderObjectWidget( + children: children, + mapDataSource: shapeFileData.mapDataSource, + mapSource: widget.source, + selectedIndex: widget.selectedIndex, + legend: _legendWidget, + selectionSettings: widget.selectionSettings, + zoomPanBehavior: widget.zoomPanBehavior, + bubbleSettings: widget.bubbleSettings.copyWith( + color: _mapsThemeData.bubbleColor, + strokeColor: _mapsThemeData.bubbleStrokeColor, + strokeWidth: _mapsThemeData.bubbleStrokeWidth), + themeData: _mapsThemeData, + controller: controller, + state: this, + ); + } + + Widget get _actualChild { + if (_legendConfiguration != null) { + _updateLegendWidget(); + if (_legendConfiguration.offset == null) { + switch (_legendConfiguration.position) { + case MapLegendPosition.top: + return Column( + children: [ + _legendWidget, + _expandedShapeLayerWidget, + ], + ); + case MapLegendPosition.bottom: + return Column( + children: [ + _expandedShapeLayerWidget, + _legendWidget, + ], + ); + case MapLegendPosition.left: + return Row( + children: [ + _legendWidget, + _expandedShapeLayerWidget, + ], + ); + case MapLegendPosition.right: + return Row( + children: [ + _expandedShapeLayerWidget, + _legendWidget, + ], + ); + } + } else { + return _stackedLegendAndShapeLayerWidget; + } + } + + return _shapeLayerRenderObjectWidget; + } + + void _updateLegendWidget() { + _legendWidget = _legendWidget.copyWith( + dataSource: _getLegendSource() ?? shapeFileData.mapDataSource, + legend: _legendConfiguration, + themeData: _mapsThemeData, + controller: controller, + toggleAnimationController: toggleAnimationController, + ); + } + + List _getLegendSource() { + switch (widget.legend.source) { + case MapElement.bubble: + return widget.source.bubbleColorMappers; + break; + case MapElement.shape: + return widget.source.shapeColorMappers; + break; + } + return null; + } + + Widget get _expandedShapeLayerWidget => Expanded( + child: Stack( + alignment: Alignment.center, + children: [_shapeLayerRenderObjectWidget], + ), + ); + + /// Returns the legend and map overlapping widget. + Widget get _stackedLegendAndShapeLayerWidget => Stack( + children: [ + _shapeLayerRenderObjectWidget, + Align( + alignment: _getActualLegendAlignment(_legendConfiguration.position), + // Padding widget is used to set the custom position to the legend. + child: Padding( + padding: _getActualLegendOffset(context), + child: _legendWidget, + ), + ), + ], + ); + + /// Returns the alignment for the legend if we set the legend offset. + AlignmentGeometry _getActualLegendAlignment(MapLegendPosition position) { + switch (position) { + case MapLegendPosition.top: + return Alignment.topCenter; + break; + case MapLegendPosition.bottom: + return Alignment.bottomCenter; + break; + case MapLegendPosition.left: + return Alignment.centerLeft; + break; + case MapLegendPosition.right: + return Alignment.centerRight; + break; + } + return Alignment.topCenter; + } + + /// Returns the padding value to render the legend based on offset value. + EdgeInsetsGeometry _getActualLegendOffset(BuildContext context) { + final Offset offset = _legendConfiguration.offset; + final MapLegendPosition legendPosition = + _legendConfiguration.position ?? MapLegendPosition.top; + // Here the default alignment is center for all the positions. + // So need to handle the offset by multiplied it by 2. + switch (legendPosition) { + // Returns the insets for the offset if the legend position is top. + case MapLegendPosition.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); + break; + // Returns the insets for the offset if the legend position is left. + case MapLegendPosition.left: + return EdgeInsets.only( + top: offset.dy > 0 ? offset.dy * 2 : 0, + bottom: offset.dy < 0 ? offset.dy.abs() * 2 : 0, + left: offset.dx > 0 ? offset.dx : 0); + break; + // Returns the insets for the offset if the legend position is right. + case MapLegendPosition.right: + return EdgeInsets.only( + top: offset.dy > 0 ? offset.dy * 2 : 0, + bottom: offset.dy < 0 ? offset.dy.abs() * 2 : 0, + right: offset.dx < 0 ? offset.dx.abs() : 0); + break; + // Returns the insets for the offset if the legend position is bottom. + case MapLegendPosition.bottom: + return EdgeInsets.only( + left: offset.dx > 0 ? offset.dx * 2 : 0, + right: offset.dx < 0 ? offset.dx.abs() * 2 : 0, + bottom: offset.dy < 0 ? offset.dy.abs() : 0); + break; + } + return EdgeInsets.zero; + } + + /// Updating [modelSource] data index based on [dataMapper] + /// value and data color based on [colorValueMapper] value. + void _bindMapsSourceIntoDataSource() { + if (widget.source.dataCount != null && + widget.source.dataCount > 0 && + widget.source.primaryValueMapper != null) { + final bool hasShapeColorValueMapper = + widget.source.shapeColorValueMapper != null; + final bool hasDataLabelMapper = widget.source.dataLabelMapper != null; + final bool hasBubbleColorValueMapper = + widget.source.bubbleColorValueMapper != null; + final bool hasBubbleSizeMapper = widget.source.bubbleSizeMapper != null; + + for (int i = 0; i < widget.source.dataCount; i++) { + final MapModel mapModel = + shapeFileData.mapDataSource[widget.source.primaryValueMapper(i)]; + if (mapModel != null) { + mapModel.dataIndex = i; + _updateShapeColor(hasShapeColorValueMapper, i, mapModel); + if (hasDataLabelMapper) { + mapModel.dataLabelText = widget.source.dataLabelMapper(i); + } + + _updateBubbleColor(hasBubbleColorValueMapper, i, mapModel); + _validateBubbleSize(hasBubbleSizeMapper, i, mapModel); + if (mapModel.dataIndex == widget.selectedIndex) { + mapModel.isSelected = true; + shapeFileData.initialSelectedModel = mapModel; + } + } + } + } + } + + void _updateShapeColor( + bool hasShapeColorValueMapper, int index, MapModel mapModel) { + if (hasShapeColorValueMapper) { + mapModel.shapeColor = _getActualColor( + widget.source.shapeColorValueMapper(index), + widget.source.shapeColorMappers, + mapModel); + } + } + + void _updateBubbleColor( + bool hasBubbleColorValueMapper, int index, MapModel mapModel) { + if (hasBubbleColorValueMapper) { + mapModel.bubbleColor = _getActualColor( + widget.source.bubbleColorValueMapper(index), + widget.source.bubbleColorMappers, + mapModel); + } + } + + void _validateBubbleSize( + bool hasBubbleSizeMapper, int index, MapModel mapModel) { + if (hasBubbleSizeMapper) { + mapModel.bubbleSizeValue = widget.source.bubbleSizeMapper(index); + if (mapModel.bubbleSizeValue != null) { + if (minBubbleValue == null) { + minBubbleValue = mapModel.bubbleSizeValue; + maxBubbleValue = mapModel.bubbleSizeValue; + } else { + minBubbleValue = min(mapModel.bubbleSizeValue, minBubbleValue); + maxBubbleValue = max(mapModel.bubbleSizeValue, maxBubbleValue); + } + } + } + } + + /// Returns color from [MapColorMapper] based on the data source value. + Color _getActualColor( + Object colorValue, List colorMappers, MapModel mapModel) { + MapColorMapper mapper; + final int length = colorMappers != null ? colorMappers.length : 0; + // Handles equal color mapping. + if (colorValue is String) { + for (int i = 0; i < length; i++) { + mapper = colorMappers[i]; + assert(mapper.value != null && mapper.color != null); + if (mapper.value == colorValue) { + mapModel?.legendMapperIndex = i; + return mapper.color; + } + } + } + + // Handles range color mapping. + if (colorValue is num) { + for (int i = 0; i < length; i++) { + mapper = colorMappers[i]; + assert( + mapper.from != null && mapper.to != null && mapper.color != null); + if (mapper.from <= colorValue && mapper.to >= colorValue) { + mapModel?.legendMapperIndex = i; + if (mapper.minOpacity != null && mapper.maxOpacity != null) { + return mapper.color.withOpacity(lerpDouble( + mapper.minOpacity, + mapper.maxOpacity, + (colorValue - mapper.from) / (mapper.to - mapper.from))); + } + return mapper.color; + } + } + } + + return colorValue; + } + + void refreshMarkers([MarkerAction action, List indices]) { + MapMarker marker; + switch (action) { + case MarkerAction.insert: + int index = indices[0]; + assert(index <= widget.controller._markersCount); + if (index > widget.controller._markersCount) { + index = widget.controller._markersCount; + } + marker = widget.markerBuilder(context, index); + if (index < widget.controller._markersCount) { + _markers.insert(index, marker); + } else if (index == widget.controller._markersCount) { + _markers.add(marker); + } + widget.controller._markersCount++; + break; + case MarkerAction.removeAt: + final int index = indices[0]; + assert(index < widget.controller._markersCount); + _markers.removeAt(index); + widget.controller._markersCount--; + break; + case MarkerAction.replace: + for (final int index in indices) { + assert(index < widget.controller._markersCount); + marker = widget.markerBuilder(context, index); + _markers[index] = marker; + } + break; + case MarkerAction.clear: + _markers.clear(); + widget.controller._markersCount = 0; + break; + } + + setState(() { + // Rebuilds to visually update the markers when it was updated or added. + }); + } +} + +class _MapShapeLayerRenderObjectWidget extends Stack { + _MapShapeLayerRenderObjectWidget({ + List children, + this.mapDataSource, + this.mapSource, + this.selectedIndex, + this.legend, + this.bubbleSettings, + this.selectionSettings, + this.zoomPanBehavior, + this.themeData, + this.controller, + this.state, + }) : super(children: children ?? []); + + final Map mapDataSource; + final MapShapeSource mapSource; + final int selectedIndex; + final MapLayerLegend legend; + final MapBubbleSettings bubbleSettings; + final MapSelectionSettings selectionSettings; + final MapZoomPanBehavior zoomPanBehavior; + final SfMapsThemeData themeData; + final MapController controller; + final _MapsShapeLayerState state; + + @override + RenderStack createRenderObject(BuildContext context) { + return RenderShapeLayer( + mapDataSource: mapDataSource, + mapSource: mapSource, + selectedIndex: selectedIndex, + legend: legend, + bubbleSettings: bubbleSettings, + selectionSettings: selectionSettings, + zoomPanBehavior: zoomPanBehavior, + themeData: themeData, + controller: controller, + context: context, + state: state, + ); + } + + @override + void updateRenderObject(BuildContext context, RenderShapeLayer renderObject) { + renderObject + ..mapDataSource = mapDataSource + ..mapSource = mapSource + ..selectedIndex = selectedIndex + ..legend = legend + ..bubbleSettings = bubbleSettings + ..selectionSettings = selectionSettings + ..zoomPanBehavior = zoomPanBehavior + ..themeData = themeData + ..context = context; + } +} + +// ignore_for_file: public_member_api_docs +class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { + RenderShapeLayer({ + Map mapDataSource, + MapShapeSource mapSource, + int selectedIndex, + MapLayerLegend legend, + MapBubbleSettings bubbleSettings, + MapSelectionSettings selectionSettings, + MapZoomPanBehavior zoomPanBehavior, + SfMapsThemeData themeData, + MapController controller, + BuildContext context, + _MapsShapeLayerState state, + }) : _mapDataSource = mapDataSource, + _mapSource = mapSource, + _selectedIndex = selectedIndex, + _legend = legend, + _bubbleSettings = bubbleSettings, + _selectionSettings = selectionSettings, + _zoomPanBehavior = zoomPanBehavior, + _themeData = themeData, + context = context, + controller = controller, + _state = state, + super(textDirection: Directionality.of(state.context)) { + _scaleGestureRecognizer = ScaleGestureRecognizer() + ..onStart = _handleScaleStart + ..onUpdate = _handleScaleUpdate + ..onEnd = _handleScaleEnd; + + if (!_state.isSublayer) { + _state.controller + ..onZoomLevelChange = _handleZoomLevelChange + ..onPanChange = _handlePanTo; + } + + _forwardToggledShapeColorTween = ColorTween(); + _forwardToggledShapeStrokeColorTween = ColorTween(); + _reverseToggledShapeColorTween = ColorTween(); + _reverseToggledShapeStrokeColorTween = ColorTween(); + + _hoverColorAnimation = CurvedAnimation( + parent: _state.hoverShapeAnimationController, curve: Curves.easeInOut); + _forwardHoverColorTween = ColorTween(); + _forwardHoverStrokeColorTween = ColorTween(); + _reverseHoverColorTween = ColorTween(); + _reverseHoverStrokeColorTween = ColorTween(); + + _toggleShapeAnimation = CurvedAnimation( + parent: _state.toggleAnimationController, curve: Curves.easeInOut); + + if (_zoomPanBehavior != null) { + _initializeZoomPanAnimations(); + _currentZoomLevel = _zoomPanBehavior.zoomLevel; + } + + if (_selectedIndex != -1) { + _currentSelectedItem = _state.shapeFileData.initialSelectedModel; + _initializeSelectionTween(); + } + + if (_legend != null && _legend.enableToggleInteraction) { + _initializeToggledShapeTweenColors(); + } + + if (hasShapeHoverColor) { + _initializeHoverTweenColors(); + } + + _state.widget.controller?._parentBox = this; + } + + final _MapsShapeLayerState _state; + final int _minPanDistance = 5; + Size _size; + double _actualFactor = 1.0; + Size _actualShapeSize; + Offset _downGlobalPoint; + Offset _downLocalPoint; + int _pointerCount = 0; + bool _singleTapConfirmed = false; + bool _isZoomedUsingToolbar = false; + MapModel _prevSelectedItem; + MapModel _currentSelectedItem; + MapModel _currentHoverItem; + MapModel _previousHoverItem; + MapModel _currentInteractedItem; + MapLayerElement _currentInteractedElement; + ScaleGestureRecognizer _scaleGestureRecognizer; + Animation _selectionColorAnimation; + Animation _toggleShapeAnimation; + Timer _zoomingDelayTimer; + Rect _referenceShapeBounds; + Rect _referenceVisibleBounds; + MapZoomDetails _zoomDetails; + MapPanDetails _panDetails; + bool _avoidPanUpdate = false; + double _currentZoomLevel = 1.0; + + Animation _hoverColorAnimation; + ColorTween _forwardSelectionColorTween; + ColorTween _forwardSelectionStrokeColorTween; + ColorTween _reverseSelectionColorTween; + ColorTween _reverseSelectionStrokeColorTween; + ColorTween _forwardHoverColorTween; + ColorTween _forwardHoverStrokeColorTween; + ColorTween _reverseHoverColorTween; + ColorTween _reverseHoverStrokeColorTween; + ColorTween _forwardToggledShapeColorTween; + ColorTween _forwardToggledShapeStrokeColorTween; + ColorTween _reverseToggledShapeColorTween; + ColorTween _reverseToggledShapeStrokeColorTween; + + Animation _zoomLevelAnimation; + Animation _focalLatLngAnimation; + MapLatLngTween _focalLatLngTween; + Tween _zoomLevelTween; + + BuildContext context; + MapController controller; + + bool get canZoom => + _zoomPanBehavior != null && + (_zoomPanBehavior.enablePinching || _zoomPanBehavior.enablePanning); + + bool get isInteractive => + _state.widget.shapeTooltipBuilder != null || + _state.widget.bubbleTooltipBuilder != null || + _state.widget.onSelectionChanged != null || + (_state.isDesktop && (hasBubbleHoverColor || hasShapeHoverColor)); + + bool get hasBubbleHoverColor => + _themeData.bubbleHoverColor != Colors.transparent || + (_themeData.bubbleHoverStrokeColor != Colors.transparent && + _themeData.bubbleHoverStrokeWidth > 0); + + bool get hasShapeHoverColor => + _themeData.shapeHoverColor != Colors.transparent || + (_themeData.shapeHoverStrokeColor != Colors.transparent && + _themeData.shapeHoverStrokeWidth > 0); + + Map get mapDataSource => _mapDataSource; + Map _mapDataSource; + set mapDataSource(Map value) { + if (const MapEquality().equals(_mapDataSource, value)) { + return; + } + + _mapDataSource = value; + _refresh(); + markNeedsPaint(); + } + + MapShapeSource get mapSource => _mapSource; + MapShapeSource _mapSource; + set mapSource(MapShapeSource value) { + if (_mapSource == value) { + return; + } + + if (_mapSource != null && + value != null && + _mapSource._mapProvider != value._mapProvider) { + _mapSource = value; + return; + } + + _mapSource = value; + _currentSelectedItem = null; + _prevSelectedItem = null; + _previousHoverItem = null; + _refresh(); + markNeedsPaint(); + _state.dataLabelAnimationController.value = 0.0; + _state.bubbleAnimationController.value = 0.0; + SchedulerBinding.instance.addPostFrameCallback(_initiateInitialAnimations); + } + + MapBubbleSettings get bubbleSettings => _bubbleSettings; + MapBubbleSettings _bubbleSettings; + set bubbleSettings(MapBubbleSettings value) { + if (_bubbleSettings == value) { + return; + } + if (_bubbleSettings.minRadius != value.minRadius || + _bubbleSettings.maxRadius != value.maxRadius) { + _bubbleSettings = value; + _mapDataSource.forEach((String key, MapModel mapModel) { + _updateBubbleRadiusAndPath(mapModel); + }); + } else { + _bubbleSettings = value; + } + markNeedsPaint(); + } + + MapLayerLegend get legend => _legend; + MapLayerLegend _legend; + set legend(MapLayerLegend value) { + // Update [MapsShapeLayer.legend] value only when + // [MapsShapeLayer.legend] property is set to shape. + if (_legend != null && _legend.source != MapElement.shape || + _legend == value) { + return; + } + _legend = value; + if (_legend.enableToggleInteraction) { + _initializeToggledShapeTweenColors(); + } + markNeedsPaint(); + } + + MapSelectionSettings get selectionSettings => _selectionSettings; + MapSelectionSettings _selectionSettings; + set selectionSettings(MapSelectionSettings value) { + if (_selectionSettings == value) { + return; + } + _selectionSettings = value; + } + + MapZoomPanBehavior get zoomPanBehavior => _zoomPanBehavior; + MapZoomPanBehavior _zoomPanBehavior; + set zoomPanBehavior(MapZoomPanBehavior value) { + if (_zoomPanBehavior == value) { + return; + } + _zoomPanBehavior = value; + if (_zoomPanBehavior != null) { + if (_zoomLevelAnimation == null) { + _initializeZoomPanAnimations(); + } + _currentZoomLevel = _zoomPanBehavior.zoomLevel; + } + } + + int get selectedIndex => _selectedIndex; + int _selectedIndex; + set selectedIndex(int value) { + if (_selectedIndex == value) { + return; + } + + assert(_selectedIndex != null); + _selectedIndex = value; + if (_forwardSelectionColorTween == null) { + _initializeSelectionTween(); + } + + _handleShapeLayerSelection(); + } + + SfMapsThemeData get themeData => _themeData; + SfMapsThemeData _themeData; + set themeData(SfMapsThemeData value) { + if (_themeData == value) { + return; + } + _themeData = value; + + if (_forwardSelectionColorTween != null) { + _updateSelectionTweenColors(); + } + if (_legend != null && _legend.enableToggleInteraction) { + _initializeToggledShapeTweenColors(); + } + + if (hasShapeHoverColor) { + _initializeHoverTweenColors(); + } + + markNeedsPaint(); + } + + @override + Rect get paintBounds => Offset.zero & _size; + + @override + MouseCursor get cursor => controller.gesture == Gesture.pan + ? SystemMouseCursors.grabbing + : SystemMouseCursors.basic; + + @override + PointerEnterEventListener get onEnter => null; + + // As onHover property of MouseHoverAnnotation was removed only in the + // beta channel, once it is moved to stable, will remove this property. + @override + // ignore: override_on_non_overriding_member + PointerHoverEventListener get onHover => null; + + @override + PointerExitEventListener get onExit => _handleExit; + + @override + // ignore: override_on_non_overriding_member + bool get validForMouseTracker => true; + + void _initializeZoomPanAnimations() { + _zoomLevelAnimation = CurvedAnimation( + parent: _state.zoomLevelAnimationController, curve: Curves.easeInOut); + _focalLatLngAnimation = CurvedAnimation( + parent: _state.focalLatLngAnimationController, curve: Curves.easeInOut); + _focalLatLngTween = MapLatLngTween(); + _zoomLevelTween = Tween(); + } + + void _initializeSelectionTween() { + _selectionColorAnimation = CurvedAnimation( + parent: _state.selectionAnimationController, curve: Curves.easeInOut); + _forwardSelectionColorTween = ColorTween(); + _forwardSelectionStrokeColorTween = ColorTween(); + _reverseSelectionColorTween = ColorTween(); + _reverseSelectionStrokeColorTween = ColorTween(); + _updateSelectionTweenColors(); + } + + void _updateSelectionTweenColors() { + final Color selectionColor = _themeData.selectionColor; + _forwardSelectionColorTween.end = selectionColor; + _forwardSelectionStrokeColorTween.begin = _themeData.layerStrokeColor; + _forwardSelectionStrokeColorTween.end = _themeData.selectionStrokeColor; + + _reverseSelectionColorTween.begin = selectionColor; + _reverseSelectionStrokeColorTween.begin = _themeData.selectionStrokeColor; + _reverseSelectionStrokeColorTween.end = _themeData.layerStrokeColor; + _updateCurrentSelectedItemTween(); + } + + void _updateCurrentSelectedItemTween() { + if (_currentSelectedItem != null && + (_state.isSublayer || !controller.wasToggled(_currentSelectedItem))) { + _forwardSelectionColorTween.begin = + getActualShapeColor(_currentSelectedItem); + } + + if (_prevSelectedItem != null) { + _reverseSelectionColorTween.end = getActualShapeColor(_prevSelectedItem); + } + } + + void _initializeHoverTweenColors() { + final Color hoverStrokeColor = _getHoverStrokeColor(); + _forwardHoverStrokeColorTween.begin = _themeData.layerStrokeColor; + _forwardHoverStrokeColorTween.end = hoverStrokeColor; + _reverseHoverStrokeColorTween.begin = hoverStrokeColor; + _reverseHoverStrokeColorTween.end = _themeData.layerStrokeColor; + } + + Color _getHoverStrokeColor() { + final bool canAdjustHoverOpacity = + double.parse(_themeData.layerStrokeColor.opacity.toStringAsFixed(2)) != + hoverColorOpacity; + return _themeData.shapeHoverStrokeColor != null && + _themeData.shapeHoverStrokeColor != Colors.transparent + ? _themeData.shapeHoverStrokeColor + : _themeData.layerStrokeColor.withOpacity( + canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); + } + + void _refresh([MapLatLng latlng]) { + if (hasSize && _mapDataSource != null && _mapDataSource.isNotEmpty) { + if (_state.isSublayer) { + // For tile layer's sublayer. + if (controller.isTileLayerChild) { + _size = controller.getTileSize(); + latlng = controller.visibleFocalLatLng; + } + // For shape layer's sublayer. + else { + _updateMapDataSourceForVisual(); + markNeedsPaint(); + return; + } + } + _computeActualFactor(); + controller.shapeLayerSizeFactor = _actualFactor; + if (_zoomPanBehavior != null) { + controller.shapeLayerSizeFactor *= _zoomPanBehavior.zoomLevel; + final double inflateWidth = + _size.width * _zoomPanBehavior.zoomLevel / 2 - _size.width / 2; + final double inflateHeight = + _size.height * _zoomPanBehavior.zoomLevel / 2 - _size.height / 2; + controller.shapeLayerOrigin = Offset( + paintBounds.left - inflateWidth, paintBounds.top - inflateHeight); + } + + controller.shapeLayerOffset = + _getTranslationPoint(controller.shapeLayerSizeFactor); + final Offset offsetBeforeAdjust = controller.shapeLayerOffset; + _adjustLayerOffsetTo(latlng); + if (!_state.isSublayer) { + controller.shapeLayerOrigin += + controller.shapeLayerOffset - offsetBeforeAdjust; + controller.updateVisibleBounds(); + } + + _updateMapDataSourceForVisual(); + markNeedsPaint(); + } + } + + void _adjustLayerOffsetTo(MapLatLng latlng) { + latlng ??= _zoomPanBehavior?.focalLatLng; + if (latlng != null) { + final Offset focalPoint = pixelFromLatLng( + latlng.latitude, + latlng.longitude, + _size, + controller.shapeLayerOffset, + controller.shapeLayerSizeFactor, + ); + final Offset center = + _getShapeBounds(controller.shapeLayerSizeFactor).center; + controller.shapeLayerOffset += + center + controller.shapeLayerOffset - focalPoint; + } + } + + void _computeActualFactor() { + final Offset minPoint = pixelFromLatLng( + _state.shapeFileData.bounds.minLatitude, + _state.shapeFileData.bounds.minLongitude, + _size); + final Offset maxPoint = pixelFromLatLng( + _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); + } + + Offset _getTranslationPoint(double factor, [Rect bounds]) { + assert(factor != null); + bounds ??= _getShapeBounds(factor); + // 0.0 is default translation value. + final double dx = interpolateValue( + 0.0, _size.width - _actualShapeSize.width, -bounds.left); + final double dy = interpolateValue( + 0.0, _size.height - _actualShapeSize.height, -bounds.top); + final Size widgetSize = _state.isSublayer ? size : _size; + final Offset shift = Offset( + 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); + final Offset maxPoint = pixelFromLatLng( + _state.shapeFileData.bounds.maxLatitude, + _state.shapeFileData.bounds.maxLongitude, + _size, + translation, + factor); + return Rect.fromPoints(minPoint, maxPoint); + } + + void _updateMapDataSourceForVisual() { + if (_mapDataSource != null) { + Offset point; + Path shapePath; + dynamic coordinate; + List pixelPoints; + List rawPoints; + int rawPointsLength, pointsLength; + _mapDataSource.forEach((String key, MapModel mapModel) { + double signedArea = 0.0, centerX = 0.0, centerY = 0.0; + rawPointsLength = mapModel.rawPoints.length; + mapModel.pixelPoints = List>(rawPointsLength); + shapePath = Path(); + for (int j = 0; j < rawPointsLength; j++) { + rawPoints = mapModel.rawPoints[j]; + pointsLength = rawPoints.length; + pixelPoints = mapModel.pixelPoints[j] = List(pointsLength); + for (int k = 0; k < pointsLength; k++) { + coordinate = rawPoints[k]; + point = pixelPoints[k] = pixelFromLatLng( + coordinate[1], + coordinate[0], + _size, + controller.shapeLayerOffset, + controller.shapeLayerSizeFactor); + if (k == 0) { + shapePath.moveTo(point.dx, point.dy); + } else { + shapePath.lineTo(point.dx, point.dy); + final int l = k - 1; + if ((_state.widget.showDataLabels || + _state.widget.source.bubbleSizeMapper != null) && + l + 1 < pixelPoints.length) { + // Used mathematical formula to find + // the center of polygon points. + final double x0 = pixelPoints[l].dx, y0 = pixelPoints[l].dy; + final double x1 = pixelPoints[l + 1].dx, + y1 = pixelPoints[l + 1].dy; + signedArea += (x0 * y1) - (y0 * x1); + centerX += (x0 + x1) * (x0 * y1 - x1 * y0); + centerY += (y0 + y1) * (x0 * y1 - x1 * y0); + } + } + } + shapePath.close(); + } + + mapModel.shapePath = shapePath; + _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) { + if (bubbleSizeValue == _state.minBubbleValue) { + mapModel.bubbleRadius = _bubbleSettings.minRadius; + } else if (bubbleSizeValue == _state.maxBubbleValue) { + mapModel.bubbleRadius = _bubbleSettings.maxRadius; + } else { + final double percentage = ((bubbleSizeValue - _state.minBubbleValue) / + (_state.maxBubbleValue - _state.minBubbleValue)) * + 100; + mapModel.bubbleRadius = bubbleSettings.minRadius + + (bubbleSettings.maxRadius - bubbleSettings.minRadius) * + (percentage / 100); + } + } + + if ((_state.widget.bubbleTooltipBuilder != null || hasBubbleHoverColor) && + mapModel.bubbleRadius != null) { + mapModel.bubblePath = Path() + ..addOval( + Rect.fromCircle( + center: mapModel.shapePathCenter, + radius: mapModel.bubbleRadius, + ), + ); + } + } + + // Invoking animation for data label and bubble. + void _initiateInitialAnimations(Duration timeStamp) { + if (_state.mounted) { + if (_state.widget.source.bubbleSizeMapper != null) { + _state.bubbleAnimationController.forward(from: 0); + } else if (_state.widget.showDataLabels) { + _state.dataLabelAnimationController.forward(from: 0); + } + } + } + + void _handleScaleStart(ScaleStartDetails details) { + if (canZoom) { + if (controller.gesture == Gesture.scale) { + _zoomEnd(); + } + + controller.isInInteractive = true; + controller.gesture = null; + _downGlobalPoint = details.focalPoint; + _downLocalPoint = details.localFocalPoint; + _referenceVisibleBounds = + controller.getVisibleBounds(controller.shapeLayerOffset); + _referenceShapeBounds = _getShapeBounds( + controller.shapeLayerSizeFactor, controller.shapeLayerOffset); + _zoomDetails = MapZoomDetails( + newVisibleBounds: controller.getVisibleLatLngBounds( + _referenceVisibleBounds.topRight, + _referenceVisibleBounds.bottomLeft, + ), + ); + _panDetails = MapPanDetails( + newVisibleBounds: _zoomDetails.newVisibleBounds, + ); + } + } + + // Scale and pan are handled in scale gesture. + void _handleScaleUpdate(ScaleUpdateDetails details) { + controller.isInInteractive = true; + controller.gesture ??= + _getGestureType(details.scale, details.localFocalPoint); + if (!canZoom || controller.gesture == null) { + return; + } + + switch (controller.gesture) { + case Gesture.scale: + _singleTapConfirmed = false; + if (_zoomPanBehavior.enablePinching && + controller.shapeLayerSizeFactor * details.scale >= _actualFactor) { + _invokeOnZooming(details.scale, _downLocalPoint, _downGlobalPoint); + } + return; + case Gesture.pan: + _singleTapConfirmed = false; + if (_zoomPanBehavior.enablePanning) { + _invokeOnPanning( + details.localFocalPoint, _downLocalPoint, details.focalPoint); + } + return; + } + } + + void _invokeOnZooming(double scale, + [Offset localFocalPoint, Offset globalFocalPoint]) { + final double newZoomLevel = _getZoomLevel(scale); + final double newShapeLayerSizeFactor = _getScale(newZoomLevel); + final Offset newShapeLayerOffset = + controller.getZoomingTranslation(origin: localFocalPoint); + final Rect newVisibleBounds = controller.getVisibleBounds( + newShapeLayerOffset, newShapeLayerSizeFactor); + _zoomDetails = MapZoomDetails( + localFocalPoint: localFocalPoint, + globalFocalPoint: globalFocalPoint, + previousZoomLevel: _zoomPanBehavior.zoomLevel, + newZoomLevel: newZoomLevel, + previousVisibleBounds: _zoomDetails != null + ? _zoomDetails.newVisibleBounds + : controller.visibleLatLngBounds, + newVisibleBounds: controller.getVisibleLatLngBounds( + newVisibleBounds.topRight, + newVisibleBounds.bottomLeft, + newShapeLayerOffset, + newShapeLayerSizeFactor, + ), + ); + if (_state.widget.onWillZoom == null || + _state.widget.onWillZoom(_zoomDetails)) { + _zoomPanBehavior?.onZooming(_zoomDetails); + } + } + + void _invokeOnPanning( + Offset localFocalPoint, Offset previousFocalPoint, Offset focalPoint, + [bool canAvoidPanUpdate = false]) { + _avoidPanUpdate = canAvoidPanUpdate; + final Offset delta = + _getValidPanDelta(localFocalPoint - previousFocalPoint); + final Rect visibleBounds = controller.getVisibleBounds( + controller.shapeLayerOffset + + (canAvoidPanUpdate ? Offset.zero : delta)); + _panDetails = MapPanDetails( + globalFocalPoint: focalPoint, + localFocalPoint: localFocalPoint, + zoomLevel: _zoomPanBehavior.zoomLevel, + delta: delta, + previousVisibleBounds: _panDetails != null + ? _panDetails.newVisibleBounds + : controller.visibleLatLngBounds, + newVisibleBounds: controller.getVisibleLatLngBounds( + visibleBounds.topRight, + visibleBounds.bottomLeft, + controller.shapeLayerOffset + + (canAvoidPanUpdate ? Offset.zero : delta)), + ); + if (_state.widget.onWillPan == null || + _state.widget.onWillPan(_panDetails)) { + _zoomPanBehavior?.onPanning(_panDetails); + } + } + + Offset _getValidPanDelta(Offset delta) { + final Rect currentShapeBounds = _getShapeBounds( + controller.shapeLayerSizeFactor, controller.shapeLayerOffset + delta); + double dx = 0.0, dy = 0.0; + if (_referenceVisibleBounds.width < _referenceShapeBounds.width) { + dx = delta.dx; + if (currentShapeBounds.left > _referenceVisibleBounds.left) { + dx = _referenceVisibleBounds.left - _referenceShapeBounds.left; + } + + if (currentShapeBounds.right < _referenceVisibleBounds.right) { + dx = _referenceVisibleBounds.right - _referenceShapeBounds.right; + } + } + + if (_referenceVisibleBounds.height < _referenceShapeBounds.height) { + dy = delta.dy; + if (currentShapeBounds.top > _referenceVisibleBounds.top) { + dy = _referenceVisibleBounds.top - _referenceShapeBounds.top; + } + + if (currentShapeBounds.bottom < _referenceVisibleBounds.bottom) { + dy = _referenceVisibleBounds.bottom - _referenceShapeBounds.bottom; + } + } + + return Offset(dx, dy); + } + + Gesture _getGestureType(double scale, Offset point) { + if (scale == 1) { + if (_downLocalPoint != null) { + final Offset distance = point - _downLocalPoint; + return distance.dx.abs() > _minPanDistance || + distance.dy.abs() > _minPanDistance + ? Gesture.pan + : null; + } + } + + return Gesture.scale; + } + + Offset _getNormalizedOffset(double zoomLevel) { + double dx = 0.0, dy = 0.0; + final Rect currentBounds = controller.currentBounds; + if (currentBounds.left > paintBounds.left) { + dx = paintBounds.left - currentBounds.left; + } + + if (currentBounds.right < paintBounds.right) { + dx = paintBounds.right - currentBounds.right; + } + + if (currentBounds.top > paintBounds.top) { + dy = paintBounds.top - currentBounds.top; + } + + if (currentBounds.bottom < paintBounds.bottom) { + dy = paintBounds.bottom - currentBounds.bottom; + } + + return Offset(dx, dy); + } + + void _validateEdges(double zoomLevel, [Offset origin]) { + final Offset leftTop = controller.getZoomingTranslation( + origin: origin, + scale: _getScale(zoomLevel), + previousOrigin: controller.shapeLayerOrigin); + controller.currentBounds = Rect.fromLTWH(leftTop.dx, leftTop.dy, + _size.width * zoomLevel, _size.height * zoomLevel); + controller.normalize = _getNormalizedOffset(zoomLevel); + } + + void _handleZooming(MapZoomDetails details) { + if (_state.isSublayer) { + markNeedsPaint(); + return; + } + + if (controller.isInInteractive && details.localFocalPoint != null) { + // Updating map while pinching and scrolling. + controller.localScale = _getScale(details.newZoomLevel); + controller.pinchCenter = details.localFocalPoint; + controller.updateVisibleBounds( + controller.getZoomingTranslation() + controller.normalize, + controller.shapeLayerSizeFactor * controller.localScale); + _validateEdges(details.newZoomLevel); + } else { + // Updating map via toolbar. + _downLocalPoint = null; + _downGlobalPoint = null; + _isZoomedUsingToolbar = true; + } + _zoomPanBehavior.zoomLevel = details.newZoomLevel; + } + + void _handlePanning(MapPanDetails details) { + if (_avoidPanUpdate) { + _avoidPanUpdate = false; + return; + } + + if (_currentHoverItem != null) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = null; + } + + if (!_state.isSublayer) { + controller.panDistance = details.delta; + controller + .updateVisibleBounds(controller.shapeLayerOffset + details.delta); + } + + markNeedsPaint(); + } + + void _handleScaleEnd(ScaleEndDetails details) { + if (controller.gesture == null) { + controller.isInInteractive = false; + return; + } + + switch (controller.gesture) { + case Gesture.scale: + _zoomEnd(); + break; + case Gesture.pan: + _panEnd(); + break; + } + + controller.gesture = null; + } + + void _handleRefresh() { + if (_state.isSublayer) { + _refresh(); + } + } + + void _zoomEnd() { + controller.isInInteractive = false; + controller.gesture = null; + _zoomingDelayTimer?.cancel(); + _zoomingDelayTimer = null; + _zoomDetails = null; + _panDetails = null; + if (_zoomPanBehavior != null && + _zoomPanBehavior.enablePinching && + !_state.isSublayer) { + controller.shapeLayerOffset = + controller.getZoomingTranslation() + controller.normalize; + controller.shapeLayerOrigin = controller.getZoomingTranslation( + previousOrigin: controller.shapeLayerOrigin) + + controller.normalize; + controller.shapeLayerSizeFactor *= controller.localScale; + _updateMapDataSourceForVisual(); + controller.notifyRefreshListeners(); + markNeedsPaint(); + } + + _downLocalPoint = null; + _downGlobalPoint = null; + controller.normalize = Offset.zero; + controller.localScale = 1.0; + } + + void _panEnd() { + controller.isInInteractive = false; + _zoomDetails = null; + _panDetails = null; + if (_zoomPanBehavior.enablePanning && !_state.isSublayer) { + controller.shapeLayerOffset += controller.panDistance; + controller.shapeLayerOrigin += controller.panDistance; + _updateMapDataSourceForVisual(); + controller.notifyRefreshListeners(); + markNeedsPaint(); + } + + _downLocalPoint = null; + _downGlobalPoint = null; + controller.gesture = null; + controller.panDistance = Offset.zero; + } + + /// Handling zooming using mouse wheel scrolling. + void _handleScrollEvent(PointerScrollEvent event) { + if (_zoomPanBehavior != null && _zoomPanBehavior.enablePinching) { + controller.isInInteractive = true; + controller.gesture ??= Gesture.scale; + if (controller.gesture != Gesture.scale) { + return; + } + + if (_currentHoverItem != null) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = null; + } + _downGlobalPoint ??= event.position; + _downLocalPoint ??= event.localPosition; + double scale = controller.localScale - (event.scrollDelta.dy / 60); + if (controller.shapeLayerSizeFactor * scale < _actualFactor) { + scale = _actualFactor / controller.shapeLayerSizeFactor; + } + + _invokeOnZooming(scale, _downLocalPoint, _downGlobalPoint); + // When the user didn't scrolled or scaled for certain time period, + // we will refresh the map to the corresponding zoom level. + _zoomingDelayTimer?.cancel(); + _zoomingDelayTimer = Timer(const Duration(milliseconds: 250), () { + _zoomEnd(); + }); + } + } + + void _handleZoomLevelChange(double zoomLevel, {MapLatLng latlng}) { + if (controller.isInInteractive && + !_state.zoomLevelAnimationController.isAnimating) { + _currentZoomLevel = zoomLevel; + markNeedsPaint(); + } else { + _zoomLevelTween.begin = _currentZoomLevel; + _zoomLevelTween.end = _zoomPanBehavior.zoomLevel; + _downLocalPoint = pixelFromLatLng( + controller.visibleFocalLatLng.latitude, + controller.visibleFocalLatLng.longitude, + size, + controller.shapeLayerOffset, + controller.shapeLayerSizeFactor); + controller.isInInteractive = true; + controller.gesture = Gesture.scale; + controller.pinchCenter = _downLocalPoint; + _state.zoomLevelAnimationController.forward(from: 0.0); + } + } + + void _handlePanTo(MapLatLng latlng) { + if (!controller.isInInteractive) { + _focalLatLngTween.begin = controller.visibleFocalLatLng; + _focalLatLngTween.end = _zoomPanBehavior.focalLatLng; + _state.focalLatLngAnimationController.forward(from: 0.0); + } + } + + void _handleReset() { + _zoomPanBehavior.zoomLevel = _zoomPanBehavior.minZoomLevel; + } + + void _handleZoomLevelAnimation() { + if (_zoomLevelTween.end != null) { + _currentZoomLevel = _zoomLevelTween.evaluate(_zoomLevelAnimation); + } + controller.localScale = _getScale(_currentZoomLevel); + controller.updateVisibleBounds( + controller.getZoomingTranslation() + controller.normalize, + controller.shapeLayerSizeFactor * controller.localScale); + _validateEdges(_currentZoomLevel); + controller.notifyRefreshListeners(); + markNeedsPaint(); + } + + void _handleFocalLatLngAnimation() { + if (_focalLatLngTween.end != null) { + controller.visibleFocalLatLng = + _focalLatLngTween.evaluate(_focalLatLngAnimation); + } + _handleZoomPanAnimation(); + } + + void _handleZoomPanAnimation() { + _validateEdges( + _currentZoomLevel, Offset(_size.width / 2, _size.height / 2)); + controller.shapeLayerOrigin = controller.getZoomingTranslation( + origin: Offset(_size.width / 2, _size.height / 2), + scale: _getScale(_currentZoomLevel), + previousOrigin: controller.shapeLayerOrigin) + + controller.normalize; + controller.shapeLayerSizeFactor = _actualFactor * _currentZoomLevel; + controller.shapeLayerOffset = + _getTranslationPoint(controller.shapeLayerSizeFactor) + + controller.normalize; + if (_currentZoomLevel != 1) { + _adjustLayerOffsetTo(controller.visibleFocalLatLng); + } + + controller.updateVisibleBounds(); + _updateMapDataSourceForVisual(); + controller.notifyRefreshListeners(); + markNeedsPaint(); + } + + void _handleExit(PointerExitEvent event) { + if (_state.widget.source.bubbleSizeMapper != null && hasBubbleHoverColor) { + final ShapeLayerChildRenderBoxBase bubbleRenderObject = + _state.bubbleKey.currentContext.findRenderObject(); + bubbleRenderObject.onExit(); + } + + if (hasShapeHoverColor && _currentHoverItem != null) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = null; + _updateHoverItemTween(); + } + + // In sublayer, we have updated [hitTestSelf] as true only if the cursor + // position lies inside a shape. If not, we will make it as false. + // When setting false to [hitTestSelf], the framework will invoke the + // [_handleExit] method in desktop. To hide the previous rendered tooltip, + // we had passed the null value for model. + _invokeTooltip(); + } + + double _getZoomLevel(double scale) { + return interpolateValue( + controller.shapeLayerSizeFactor * scale / _actualFactor, + _zoomPanBehavior.minZoomLevel, + _zoomPanBehavior.maxZoomLevel, + ); + } + + double _getScale(double zoomLevel) { + return _actualFactor * zoomLevel / controller.shapeLayerSizeFactor; + } + + void _handleTapUp(Offset localPosition) { + _handleInteraction(localPosition); + if (_currentSelectedItem != null) { + _currentHoverItem = null; + } + } + + void _handleHover(PointerHoverEvent event) { + final RenderBox renderBox = context.findRenderObject(); + final Offset localPosition = renderBox.globalToLocal(event.position); + _handleInteraction(localPosition, isHover: true); + } + + bool _isElementLiesOnPosition(Offset position) { + if (!isInteractive && (_mapDataSource == null || _mapDataSource.isEmpty)) { + return false; + } + + double bubbleRadius; + _currentInteractedItem = null; + _currentInteractedElement = null; + for (final MapModel mapModel in _mapDataSource.values) { + final bool wasToggled = controller.wasToggled(mapModel); + if (_isBubbleContains(position, mapModel)) { + _currentInteractedElement = MapLayerElement.bubble; + if (!wasToggled && + (bubbleRadius == null || mapModel.bubbleRadius < bubbleRadius)) { + bubbleRadius = mapModel.bubbleRadius; + _currentInteractedItem = mapModel; + } + } else if (_isShapeContains( + position, mapModel, _currentInteractedElement) && + !(wasToggled && _state.widget.legend.source == MapElement.shape)) { + _currentInteractedItem = mapModel; + _currentInteractedElement = MapLayerElement.shape; + if (!(_state.widget.bubbleTooltipBuilder != null || + hasBubbleHoverColor)) { + break; + } + } + } + + return _currentInteractedItem != null && _currentInteractedElement != null; + } + + void _handleInteraction(Offset position, {bool isHover = false}) { + if (isHover) { + _prevSelectedItem = null; + _performChildHover(position); + } else { + _invokeSelectionChangedCallback(_currentInteractedItem); + _performChildTap(position); + } + } + + bool _isBubbleContains(Offset position, MapModel mapModel) { + return (_state.widget.bubbleTooltipBuilder != null || + hasBubbleHoverColor) && + mapModel.bubblePath != null && + mapModel.bubblePath.contains(position); + } + + bool _isShapeContains( + Offset position, MapModel mapModel, MapLayerElement element) { + return (_state.widget.onSelectionChanged != null || + _state.widget.shapeTooltipBuilder != null || + hasShapeHoverColor) && + element != MapLayerElement.bubble && + mapModel.shapePath.contains(position); + } + + void _invokeSelectionChangedCallback(MapModel mapModel) { + if (_state.widget.onSelectionChanged != null && + mapModel != null && + mapModel.dataIndex != null) { + _state.widget.onSelectionChanged(mapModel.dataIndex); + } + } + + void _performChildTap(Offset position) { + if (_currentInteractedItem != null) { + _invokeTooltip( + position: position, + model: _currentInteractedItem, + element: _currentInteractedElement); + } + } + + void _performChildHover(Offset position) { + _invokeTooltip( + position: position, + model: _currentInteractedItem, + element: _currentInteractedElement); + if (_state.widget.source.bubbleSizeMapper != null) { + final ShapeLayerChildRenderBoxBase bubbleRenderObject = + _state.bubbleKey.currentContext.findRenderObject(); + bubbleRenderObject.onHover( + _currentInteractedItem, _currentInteractedElement); + } + + if (hasShapeHoverColor && + (_currentSelectedItem == null || + _currentSelectedItem != _currentInteractedItem)) { + if (_currentInteractedElement == MapLayerElement.shape && + _currentHoverItem != _currentInteractedItem) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = _currentInteractedItem; + _updateHoverItemTween(); + } else if ((_currentHoverItem != null && + _currentHoverItem != _currentInteractedItem) || + (_currentInteractedElement == MapLayerElement.bubble && + _currentHoverItem == _currentInteractedItem)) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = null; + _updateHoverItemTween(); + } + } + } + + void _invokeTooltip( + {MapModel model, Offset position, MapLayerElement element}) { + if ((_state.widget.shapeTooltipBuilder != null || + _state.widget.bubbleTooltipBuilder != null)) { + Rect elementRect; + final ShapeLayerChildRenderBoxBase tooltipRenderObject = + controller.tooltipKey.currentContext.findRenderObject(); + if (model != null && element == MapLayerElement.bubble) { + elementRect = Rect.fromCircle( + center: model.shapePathCenter, radius: model.bubbleRadius); + } + int sublayerIndex; + if (_state.isSublayer) { + final RenderSublayerContainer sublayerContainer = parent; + sublayerIndex = + sublayerContainer.getSublayerIndex(_state.widget.sublayer); + } + + // The elementRect is not applicable, if the actual element is shape. The + // sublayerIndex is not applicable, if the actual layer is shape layer. + tooltipRenderObject.paintTooltip( + model?.dataIndex, elementRect, element, sublayerIndex, position); + } + } + + void _updateHoverItemTween() { + if (_currentHoverItem != null) { + _forwardHoverColorTween.begin = getActualShapeColor(_currentHoverItem); + _forwardHoverColorTween.end = _getHoverFillColor(_currentHoverItem); + } + + if (_previousHoverItem != null) { + _reverseHoverColorTween.begin = _getHoverFillColor(_previousHoverItem); + _reverseHoverColorTween.end = getActualShapeColor(_previousHoverItem); + } + + _state.hoverShapeAnimationController.forward(from: 0); + } + + Color _getHoverFillColor(MapModel model) { + final bool canAdjustHoverOpacity = + double.parse(getActualShapeColor(model).opacity.toStringAsFixed(2)) != + hoverColorOpacity; + return _themeData.shapeHoverColor != null && + _themeData.shapeHoverColor != Colors.transparent + ? _themeData.shapeHoverColor + : getActualShapeColor(model).withOpacity( + canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); + } + + void _handleShapeLayerSelection() { + assert(_selectedIndex < _mapSource.dataCount); + _prevSelectedItem = _currentSelectedItem; + if (_selectedIndex == -1) { + if (_prevSelectedItem != null) { + _prevSelectedItem.isSelected = false; + } + + _currentSelectedItem = null; + } else { + _currentSelectedItem = _mapDataSource.values.firstWhere( + (MapModel element) => element.dataIndex == _selectedIndex); + _currentSelectedItem.isSelected = !_currentSelectedItem.isSelected; + if (_prevSelectedItem != null) { + _prevSelectedItem.isSelected = false; + } + } + + _updateCurrentSelectedItemTween(); + _state.selectionAnimationController.forward(from: 0); + } + + void _initializeToggledShapeTweenColors() { + final Color toggledShapeColor = _themeData.toggledItemColor != + Colors.transparent + ? _themeData.toggledItemColor.withOpacity(_legend.toggledItemOpacity) + : null; + + _forwardToggledShapeColorTween.end = toggledShapeColor; + _forwardToggledShapeStrokeColorTween.begin = _themeData.layerStrokeColor; + _forwardToggledShapeStrokeColorTween.end = + _themeData.toggledItemStrokeColor != Colors.transparent + ? _themeData.toggledItemStrokeColor + : null; + + _reverseToggledShapeColorTween.begin = toggledShapeColor; + _reverseToggledShapeStrokeColorTween.begin = + _themeData.toggledItemStrokeColor != Colors.transparent + ? _themeData.toggledItemStrokeColor + : null; + _reverseToggledShapeStrokeColorTween.end = _themeData.layerStrokeColor; + } + + void _handleToggleChange() { + _previousHoverItem = null; + if (_state.widget.legend != null && + _state.widget.legend.source == MapElement.shape) { + MapModel model; + if (_state.widget.source.shapeColorMappers == null) { + model = + mapDataSource.values.elementAt(controller.currentToggledItemIndex); + } else { + for (final mapModel in _mapDataSource.values) { + if (mapModel.dataIndex != null && + mapModel.legendMapperIndex == + controller.currentToggledItemIndex) { + model = mapModel; + break; + } + } + } + + final Color shapeColor = (_currentSelectedItem != null && + _currentSelectedItem.actualIndex == model.actualIndex) + ? _themeData.selectionColor + : getActualShapeColor(model); + _forwardToggledShapeColorTween.begin = shapeColor; + _reverseToggledShapeColorTween.end = shapeColor; + _state.toggleAnimationController.forward(from: 0); + } + } + + void _handleZoomLevelAnimationStatusChange(AnimationStatus status) { + if (status == AnimationStatus.completed && _zoomLevelTween.end != null) { + _zoomEnd(); + if (!_isZoomedUsingToolbar) { + _invokeOnZooming(_getScale(_zoomPanBehavior.zoomLevel)); + } + _isZoomedUsingToolbar = false; + } + } + + void _handleFocalLatLngAnimationStatusChange(AnimationStatus status) { + if (status == AnimationStatus.completed && _focalLatLngTween.end != null) { + _referenceVisibleBounds = + controller.getVisibleBounds(controller.shapeLayerOffset); + _referenceShapeBounds = _getShapeBounds( + controller.shapeLayerSizeFactor, controller.shapeLayerOffset); + final Offset localFocalPoint = pixelFromLatLng( + _focalLatLngTween.end.latitude, + _focalLatLngTween.end.longitude, + size, + controller.shapeLayerOffset, + controller.shapeLayerSizeFactor); + final Offset previousFocalPoint = pixelFromLatLng( + _focalLatLngTween.begin.latitude, + _focalLatLngTween.begin.longitude, + size, + controller.shapeLayerOffset, + controller.shapeLayerSizeFactor); + _invokeOnPanning(localFocalPoint, previousFocalPoint, + localToGlobal(localFocalPoint), true); + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _state.selectionAnimationController?.addListener(markNeedsPaint); + _state.toggleAnimationController?.addListener(markNeedsPaint); + _state.hoverShapeAnimationController?.addListener(markNeedsPaint); + if (_state.isSublayer && controller == null) { + final RenderSublayerContainer sublayerContainer = parent; + controller = sublayerContainer.controller; + } + + if (controller != null) { + controller + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addResetListener(_handleReset); + if (_state.isSublayer) { + controller.addRefreshListener(_handleRefresh); + } else { + controller.addToggleListener(_handleToggleChange); + if (_state.zoomLevelAnimationController != null) { + _state.zoomLevelAnimationController + ..addListener(_handleZoomLevelAnimation) + ..addStatusListener(_handleZoomLevelAnimationStatusChange); + } + + if (_state.focalLatLngAnimationController != null) { + _state.focalLatLngAnimationController + ..addListener(_handleFocalLatLngAnimation) + ..addStatusListener(_handleFocalLatLngAnimationStatusChange); + } + } + } + SchedulerBinding.instance.addPostFrameCallback(_initiateInitialAnimations); + } + + @override + void detach() { + _state.dataLabelAnimationController.value = 0.0; + _state.bubbleAnimationController.value = 0.0; + _state.selectionAnimationController?.removeListener(markNeedsPaint); + _state.toggleAnimationController?.removeListener(markNeedsPaint); + _state.hoverShapeAnimationController?.removeListener(markNeedsPaint); + if (controller != null) { + controller + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeResetListener(_handleReset); + if (_state.isSublayer) { + controller.removeRefreshListener(_handleRefresh); + } else { + controller.removeToggleListener(_handleToggleChange); + if (_state.zoomLevelAnimationController != null) { + _state.zoomLevelAnimationController + ..removeListener(_handleZoomLevelAnimation) + ..removeStatusListener(_handleZoomLevelAnimationStatusChange); + } + + if (_state.focalLatLngAnimationController != null) { + _state.focalLatLngAnimationController + ..removeListener(_handleFocalLatLngAnimation) + ..removeStatusListener(_handleFocalLatLngAnimationStatusChange); + } + } + } + _zoomingDelayTimer?.cancel(); + super.detach(); + } + + @override + bool hitTestSelf(Offset position) { + final bool hasHitTestSelf = _isElementLiesOnPosition(position); + return canZoom || hasHitTestSelf; + } + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + _zoomPanBehavior?.handleEvent(event); + if ((isInteractive || canZoom) && event.down && event is PointerDownEvent) { + _pointerCount++; + if (canZoom) { + _scaleGestureRecognizer.addPointer(event); + } + _singleTapConfirmed = _pointerCount == 1; + } else if (event is PointerUpEvent || event is PointerCancelEvent) { + if (_singleTapConfirmed) { + _handleTapUp(event.localPosition); + _downLocalPoint = null; + _downGlobalPoint = null; + } + + _pointerCount = 0; + } else if (event is PointerScrollEvent) { + _handleScrollEvent(event); + } else if (_state.isDesktop && event is PointerHoverEvent) { + // PointerHoverEvent is applicable only for web platform. + _handleHover(event); + } else if (event is PointerMoveEvent && event.delta != Offset.zero) { + // In sublayer, we haven't handled the scale gestures. If we start panning + // on a sublayer shape, it takes the tap down and when tap up, it will + // perform tapping related event. So to avoid that, we had updated + // _singleTapConfirmed as false. + _singleTapConfirmed = false; + } + } + + @override + void performLayout() { + _size = getBoxSize(constraints); + controller.shapeLayerBoxSize = _size; + if (!hasSize || size != _size) { + size = _size; + _refresh(controller.visibleFocalLatLng); + } + + final BoxConstraints looseConstraints = BoxConstraints.loose(size); + RenderBox child = firstChild; + while (child != null) { + final StackParentData childParentData = child.parentData; + child.layout(looseConstraints); + child = childParentData.nextSibling; + } + } + + @override + bool get isRepaintBoundary => true; + + @override + void paint(PaintingContext context, Offset offset) { + if (_mapDataSource != null && _mapDataSource.isNotEmpty) { + context.canvas + ..save() + ..clipRect(offset & controller.shapeLayerBoxSize); + controller.applyTransform(context, offset); + final bool hasToggledIndices = controller.toggledIndices.isNotEmpty; + final Paint fillPaint = Paint()..isAntiAlias = true; + final Paint strokePaint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + final bool hasPrevSelectedItem = _prevSelectedItem != null && + !controller.wasToggled(_prevSelectedItem); + + final bool hasCurrentSelectedItem = _currentSelectedItem != null && + !controller.wasToggled(_currentSelectedItem); + + _mapDataSource.forEach((String key, MapModel model) { + if (_currentHoverItem != null && + _currentHoverItem.primaryKey == model.primaryKey) { + return; + } + + if (hasCurrentSelectedItem && selectedIndex == model.dataIndex) { + return; + } + + if (hasPrevSelectedItem && _prevSelectedItem.primaryKey == key) { + fillPaint.color = + _reverseSelectionColorTween.evaluate(_selectionColorAnimation); + strokePaint + ..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); + + if (_themeData.shapeHoverStrokeWidth > 0.0 && + _themeData.shapeHoverStrokeColor != Colors.transparent) { + strokePaint + ..color = + _reverseHoverStrokeColorTween.evaluate(_hoverColorAnimation) + ..strokeWidth = _themeData.layerStrokeWidth; + } else { + strokePaint + ..color = _themeData.layerStrokeColor + ..strokeWidth = _themeData.layerStrokeWidth; + } + } else { + _updateFillColor(model, fillPaint, hasToggledIndices); + _updateStrokePaint(model, strokePaint, hasToggledIndices); + } + + context.canvas.drawPath(model.shapePath, fillPaint); + if (strokePaint.strokeWidth > 0.0 && + strokePaint.color != Colors.transparent) { + strokePaint.strokeWidth = + _getIntrinsicStrokeWidth(strokePaint.strokeWidth); + context.canvas.drawPath(model.shapePath, strokePaint); + } + }); + + _drawHoverShape(context, fillPaint, strokePaint); + _drawSelectedShape(context, fillPaint, strokePaint); + context.canvas.restore(); + super.paint(context, offset); + } + } + + // Returns the color to the shape based on the [shapeColorMappers] and + // [layerColor] properties. + Color getActualShapeColor(MapModel model) { + return model.shapeColor ?? _themeData.layerColor; + } + + double _getIntrinsicStrokeWidth(double strokeWidth) { + return strokeWidth /= + controller.gesture == Gesture.scale ? controller.localScale : 1; + } + + // Set the color to the toggled and un-toggled shapes based on + // the [legendController.toggledIndices] collection. + void _updateFillColor( + MapModel model, Paint fillPaint, bool hasToggledIndices) { + fillPaint.style = PaintingStyle.fill; + if (_state.widget.legend != null && + _state.widget.legend.source == MapElement.shape) { + if (controller.currentToggledItemIndex == model.legendMapperIndex) { + final Color shapeColor = controller.wasToggled(model) + ? _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 + // un-toggled, then the [_reverseToggledShapeColorTween] return. + fillPaint.color = shapeColor ?? Colors.transparent; + return; + } else if (hasToggledIndices && controller.wasToggled(model)) { + // Set toggled color to the previously toggled shapes. + fillPaint.color = + _forwardToggledShapeColorTween.end ?? Colors.transparent; + return; + } + } + + fillPaint.color = getActualShapeColor(model); + } + + // 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) { + if (_state.widget.legend != null && + _state.widget.legend.source == MapElement.shape) { + if (controller.currentToggledItemIndex == model.legendMapperIndex) { + 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) + ? _legend.toggledItemStrokeWidth + : _themeData.layerStrokeWidth; + return; + } else if (hasToggledIndices && controller.wasToggled(model)) { + // Set toggled stroke color to the previously toggled shapes. + strokePaint + ..color = + _forwardToggledShapeStrokeColorTween.end ?? Colors.transparent + ..strokeWidth = _legend.toggledItemStrokeWidth; + return; + } + } + + strokePaint + ..color = _themeData.layerStrokeColor + ..strokeWidth = _themeData.layerStrokeWidth; + } + + void _drawSelectedShape( + PaintingContext context, Paint fillPaint, Paint strokePaint) { + if (_currentSelectedItem != null && + !controller.wasToggled(_currentSelectedItem)) { + fillPaint.color = + _forwardSelectionColorTween.evaluate(_selectionColorAnimation); + context.canvas.drawPath(_currentSelectedItem.shapePath, fillPaint); + if (_themeData.selectionStrokeWidth > 0.0) { + strokePaint + ..color = _forwardSelectionStrokeColorTween + .evaluate(_selectionColorAnimation) + ..strokeWidth = + _getIntrinsicStrokeWidth(_themeData.selectionStrokeWidth); + context.canvas.drawPath(_currentSelectedItem.shapePath, strokePaint); + } + } + } + + void _drawHoverShape( + PaintingContext context, Paint fillPaint, Paint strokePaint) { + if (_currentHoverItem != null) { + fillPaint.color = _themeData.shapeHoverColor != Colors.transparent + ? _forwardHoverColorTween.evaluate(_hoverColorAnimation) + : getActualShapeColor(_currentHoverItem); + context.canvas.drawPath(_currentHoverItem.shapePath, fillPaint); + if (_themeData.shapeHoverStrokeWidth > 0.0 && + _themeData.shapeHoverStrokeColor != Colors.transparent) { + strokePaint + ..color = _forwardHoverStrokeColorTween.evaluate(_hoverColorAnimation) + ..strokeWidth = + _getIntrinsicStrokeWidth(_themeData.shapeHoverStrokeWidth); + } else { + strokePaint + ..color = _themeData.layerStrokeColor + ..strokeWidth = _getIntrinsicStrokeWidth(_themeData.layerStrokeWidth); + } + + if (strokePaint.strokeWidth > 0.0 && + strokePaint.color != Colors.transparent) { + context.canvas.drawPath(_currentHoverItem.shapePath, strokePaint); + } + } + } +} + +/// Converts json file to future string based on +/// assert, network, memory and file. +abstract class MapProvider { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const MapProvider(); + + /// Returns the json file as future string value. + Future loadString(); + + /// Returns shape path which is given. + String get shapePath; + + /// Returns shape bytes which is given. + Uint8List get bytes; +} + +/// Decodes the given json file as a map. +/// +/// This class behaves like similar to [Image.asset]. +/// +/// See also: +/// +/// [MapShapeSource.asset] for the [SfMaps] widget shorthand, +/// backed up by [AssetMapProvider]. +class AssetMapProvider extends MapProvider { + /// Creates an object that decodes a [String] buffer as a map. + AssetMapProvider(String assetName) + : assert(assetName != null), + assert(assetName.isNotEmpty) { + _shapePath = assetName; + } + + String _shapePath; + + @override + Future loadString() async { + return await rootBundle.loadString(_shapePath); + } + + @override + String get shapePath => _shapePath; + + @override + Uint8List get bytes => null; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is AssetMapProvider && other.shapePath == shapePath; + } + + @override + int get hashCode => hashValues(shapePath, bytes); +} + +// Decodes the given map URL from the network. +/// +/// The map will be fetched and saved in local temporary directory for map +/// manipulation. +/// +/// This class behaves like similar to [Image.network]. +/// +/// See also: +/// +/// [MapShapeSource.network] for the [SfMaps] widget shorthand, +/// backed up by [NetworkMapProvider]. +class NetworkMapProvider extends MapProvider { + /// Creates an object that decodes the map at the given URL. + NetworkMapProvider(String url) + : assert(url != null), + assert(url.isNotEmpty) { + _url = url; + } + + String _url; + + @override + Future loadString() async { + final response = await http.get(_url); + if (response.statusCode == 200) { + return response.body; + } else { + throw Exception('Failed to load json'); + } + } + + @override + String get shapePath => _url; + + @override + Uint8List get bytes => null; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is NetworkMapProvider && other.shapePath == shapePath; + } + + @override + int get hashCode => hashValues(shapePath, bytes); +} + +/// Decodes the given [Uint8List] buffer as an map. +/// +/// The provided [bytes] buffer should not be changed after it is provided +/// to a [MemoryMapProvider]. +/// +/// This class behaves like similar to [Image.memory]. +/// +/// See also: +/// +/// [MapShapeSource.memory] for the [SfMaps] widget shorthand, +/// backed up by [MemoryMapProvider]. +class MemoryMapProvider extends MapProvider { + /// Creates an object that decodes a [Uint8List] buffer as a map. + MemoryMapProvider(Uint8List bytes) : assert(bytes != null) { + _mapBytes = bytes; + } + + Uint8List _mapBytes; + + @override + Future loadString() async { + return utf8.decode(_mapBytes); + } + + @override + String get shapePath => null; + + @override + Uint8List get bytes => _mapBytes; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is MemoryMapProvider && other.bytes == bytes; + } + + @override + int get hashCode => hashValues(shapePath, bytes); +} diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer_controller.dart b/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer_controller.dart deleted file mode 100644 index 3d2e8ffc8..000000000 --- a/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer_controller.dart +++ /dev/null @@ -1,187 +0,0 @@ -part of maps; - -enum _MarkerAction { insert, removeAt, replace, clear, none } - -/// Base class of [MapShapeLayerController] and [MapTileLayerController]. -abstract class MapLayerController extends ChangeNotifier { - List _replaceableIndices; - int _markersCount; - int _index = -1; - _MarkerAction _markerAction = _MarkerAction.none; - - /// Render box of the map layer. - _RenderShapeLayer _parentBox; - - /// Returns the current markers count. - int get markersCount => _markersCount; - - /// Adds marker dynamically in the provided index. - /// - /// If the [MapShapeLayer.initialMarkersCount] is 10 and if the index given - /// for the insertMarker method is 10 which is greater than the available - /// indices, then the marker will be added as a last item. - /// - /// See also: - /// * [MapShapeLayer.markerBuilder], to return the [MapMarker]. - void insertMarker(int index) { - _markerAction = _MarkerAction.insert; - assert(index <= markersCount); - if (index > markersCount) { - index = markersCount; - } - _index = index; - notifyListeners(); - } - - /// Removes the marker in the provided index. - void removeMarkerAt(int index) { - _markerAction = _MarkerAction.removeAt; - _index = index; - notifyListeners(); - } - - /// Updates the markers in the given indices dynamically. - /// See also: - /// * [MapShapeLayer.markerBuilder], to return the updated [MapMarker]. - void updateMarkers(List indices) { - _markerAction = _MarkerAction.replace; - _replaceableIndices = indices; - notifyListeners(); - } - - /// Clears all the markers. - void clearMarkers() { - _markerAction = _MarkerAction.clear; - notifyListeners(); - } - - @override - void dispose() { - _replaceableIndices?.clear(); - _replaceableIndices = null; - - super.dispose(); - } -} - -/// Provides option for adding, removing, deleting and updating marker -/// collection. -/// -/// You can also get the current markers count and selected shape's index from -/// this. -/// -/// You need to set the instance of this to [MapShapeLayer.controller] as -/// shown in the below code snippet. -/// -/// ```dart -/// List data; -/// MapShapeLayerController controller; -/// Random random = Random(); -/// -/// @override -/// void initState() { -/// data = [ -/// Model(-14.235004, -51.92528), -/// Model(51.16569, 10.451526), -/// Model(-25.274398, 133.775136), -/// Model(20.593684, 78.96288), -/// Model(61.52401, 105.318756) -/// ]; -/// -/// controller = MapShapeLayerController(); -/// super.initState(); -/// } -/// -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// body: Center( -/// child: Container( -/// height: 350, -/// child: Padding( -/// padding: EdgeInsets.only(left: 15, right: 15), -/// child: Column( -/// children: [ -/// SfMaps( -/// layers: [ -/// MapShapeLayer( -/// delegate: MapShapeLayerDelegate( -/// shapeFile: 'assets/world_map.json', -/// shapeDataField: 'name', -/// ), -/// initialMarkersCount: 5, -/// markerBuilder: (BuildContext context, int index){ -/// return MapMarker( -/// latitude: data[index].latitude, -/// longitude: data[index].longitude, -/// child: Icon(Icons.add_location), -/// ); -/// }, -/// controller: controller, -/// ), -/// ], -/// ), -/// RaisedButton( -/// child: Text('Add marker'), -/// onPressed: () { -/// data.add(Model( -/// -180 + random.nextInt(360).toDouble(), -/// -55 + random.nextInt(139).toDouble())); -/// controller.insertMarker(5); -/// }, -/// ), -/// ], -/// ), -/// ), -/// ) -/// ), -/// ); -/// } -/// -/// class Model { -/// Model(this.latitude, this.longitude); -/// -/// final double latitude; -/// final double longitude; -/// } -/// ``` -class MapShapeLayerController extends MapLayerController { - /// Index of the shape which is selected currently. - int get selectedIndex => _selectedIndex; - int _selectedIndex; - set selectedIndex(int value) { - if (_selectedIndex == value) { - return; - } - _selectedIndex = value; - notifyListeners(); - } - - /// Convert pixel point to coordinates. - MapLatLng pixelToLatLng(Offset position) { - return _pixelToLatLng( - position, - _parentBox.size, - _parentBox.defaultController.shapeLayerOffset, - _parentBox.defaultController.shapeLayerSizeFactor); - } -} - -/// Provides an option for adding, removing, deleting and updating marker -/// collection. -class MapTileLayerController extends MapLayerController { - /// Instance of _MapTileLayerState. - _MapTileLayerState _state; - - /// Convert pixel point to coordinates. - MapLatLng pixelToLatLng(Offset position) { - final Offset localPointCenterDiff = Offset( - (_state._size.width / 2) - position.dx, - (_state._size.height / 2) - position.dy); - final Offset actualCenterPixelPosition = _getPixelFromLatLng( - _state._currentFocalLatLng, _state._currentZoomLevel); - final Offset newCenterPoint = - actualCenterPixelPosition - localPointCenterDiff; - return _getLatLngFromPixel(newCenterPoint, scale: _state._currentZoomLevel); - } -} diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer_render_box.dart b/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer_render_box.dart deleted file mode 100644 index f409101e2..000000000 --- a/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer_render_box.dart +++ /dev/null @@ -1,1504 +0,0 @@ -part of maps; - -class _RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { - _RenderShapeLayer({ - Map mapDataSource, - MapShapeLayerDelegate mapDelegate, - bool enableShapeTooltip, - bool enableBubbleTooltip, - bool enableSelection, - MapLegendSettings legendSettings, - MapBubbleSettings bubbleSettings, - MapSelectionSettings selectionSettings, - MapZoomPanBehavior zoomPanBehavior, - SfMapsThemeData themeData, - _DefaultController defaultController, - BuildContext context, - _MapsShapeLayerState state, - }) : _mapDataSource = mapDataSource, - _mapDelegate = mapDelegate, - _enableShapeTooltip = enableShapeTooltip, - _enableBubbleTooltip = enableBubbleTooltip, - _enableSelection = enableSelection, - _legendSettings = legendSettings, - _bubbleSettings = bubbleSettings, - _selectionSettings = selectionSettings, - _zoomPanBehavior = zoomPanBehavior, - _themeData = themeData, - context = context, - defaultController = defaultController, - _state = state, - super(textDirection: Directionality.of(state.context)) { - _scaleGestureRecognizer = ScaleGestureRecognizer() - ..onStart = _handleScaleStart - ..onUpdate = _handleScaleUpdate - ..onEnd = _handleScaleEnd; - - _state._defaultController - ..onZoomLevelChange = _handleZoomLevelChange - ..onPanChange = _handlePanTo; - - _selectionColorAnimation = CurvedAnimation( - parent: _state.selectionAnimationController, curve: Curves.easeInOut); - _forwardSelectionColorTween = ColorTween(); - _forwardSelectionStrokeColorTween = ColorTween(); - _reverseSelectionColorTween = ColorTween(); - _reverseSelectionStrokeColorTween = ColorTween(); - - _forwardToggledShapeColorTween = ColorTween(); - _forwardToggledShapeStrokeColorTween = ColorTween(); - _reverseToggledShapeColorTween = ColorTween(); - _reverseToggledShapeStrokeColorTween = ColorTween(); - - _hoverColorAnimation = CurvedAnimation( - parent: _state.hoverShapeAnimationController, curve: Curves.easeInOut); - _forwardHoverColorTween = ColorTween(); - _forwardHoverStrokeColorTween = ColorTween(); - _reverseHoverColorTween = ColorTween(); - _reverseHoverStrokeColorTween = ColorTween(); - - _toggleShapeAnimation = CurvedAnimation( - parent: _state.toggleAnimationController, curve: Curves.easeInOut); - - if (_enableSelection) { - _initializeSelectionTweenColors(); - if (_state.widget.initialSelectedIndex != -1) { - _currentSelectedItem = _mapDataSource.values.firstWhere( - (_MapModel model) => - model.dataIndex == _state.widget.initialSelectedIndex); - _updateCurrentSelectedItemTween(); - } - } - - if (_legendSettings.enableToggleInteraction) { - _initializeToggledShapeTweenColors(); - } - - if (hasShapeHoverColor) { - _initializeHoverTweenColors(); - } - - _state.widget.controller?._parentBox = this; - } - - final _MapsShapeLayerState _state; - final int _minPanDistance = 5; - Size _size; - double _actualFactor = 1.0; - Size _actualShapeSize; - Offset _downGlobalPoint; - Offset _downLocalPoint; - int _pointerCount = 0; - bool _singleTapConfirmed = false; - _MapModel _prevSelectedItem; - _MapModel _currentSelectedItem; - _MapModel _currentHoverItem; - _MapModel _previousHoverItem; - ScaleGestureRecognizer _scaleGestureRecognizer; - Animation _selectionColorAnimation; - Animation _toggleShapeAnimation; - Timer _zoomingDelayTimer; - Rect _refShapeBounds; - Rect _refVisibleBounds; - MapZoomDetails _zoomDetails; - MapPanDetails _panDetails; - - Animation _hoverColorAnimation; - // Apply color animation for the selected shape. The - // begin color will be shape color and the - // end color will be selection color. - ColorTween _forwardSelectionColorTween; - // Apply stroke color animation for the selected shape. The - // begin color will be shape stroke color and the - // end color will be selection stroke color. - ColorTween _forwardSelectionStrokeColorTween; - // Apply color animation for the previously selected shape. The - // begin color will be selection color and the - // end color will be shape color. - ColorTween _reverseSelectionColorTween; - // Apply stroke color animation for the previously selected shape. The - // begin color will be selection stroke color and the - // end color will be shape stroke color. - ColorTween _reverseSelectionStrokeColorTween; - // Apply color animation for the hover shape. The - // begin color will be shape color and the - // end color will be hover color. - ColorTween _forwardHoverColorTween; - // Apply stroke color animation for the hover shape. The - // begin color will be shape stroke color and the - // end color will be hover stroke color. - ColorTween _forwardHoverStrokeColorTween; - // Apply color animation for the previously hover shape. The - // begin color will be hover color and the - // end color will be shape color. - ColorTween _reverseHoverColorTween; - // Apply stroke color animation for the previously hover shape. The - // begin color will be hover stroke color and the - // end color will be shape stroke color. - ColorTween _reverseHoverStrokeColorTween; - // Apply color animation for the toggled shape. The - // begin color will be shape color and the - // end color will be toggled shape color. - ColorTween _forwardToggledShapeColorTween; - // Apply stroke color animation for the toggled shape. The - // begin color will be shape stroke color and the - // end color will be toggled shape stroke color. - ColorTween _forwardToggledShapeStrokeColorTween; - // Apply color animation for the shape while un-toggling the toggled shape. - // The begin color will be toggled shape color and the - // end color will be shape color. - ColorTween _reverseToggledShapeColorTween; - // Apply stroke color animation for the shape while un-toggling the toggled - // shape. The begin color will be toggled shape stroke color and the - // end color will be shape stroke color. - ColorTween _reverseToggledShapeStrokeColorTween; - - BuildContext context; - - _DefaultController defaultController; - - bool get canZoom => - _zoomPanBehavior != null && - (_zoomPanBehavior.enablePinching || _zoomPanBehavior.enablePanning); - - bool get isInteractive => - canZoom || - _enableBubbleTooltip || - _enableShapeTooltip || - _enableSelection || - (kIsWeb && (hasBubbleHoverColor || hasShapeHoverColor)); - - bool get hasBubbleHoverColor => - _themeData.bubbleHoverColor != Colors.transparent || - (_themeData.bubbleHoverStrokeColor != Colors.transparent && - _themeData.bubbleHoverStrokeWidth > 0); - - bool get hasShapeHoverColor => - _themeData.shapeHoverColor != Colors.transparent || - (_themeData.shapeHoverStrokeColor != Colors.transparent && - _themeData.shapeHoverStrokeWidth > 0); - - Map get mapDataSource => _mapDataSource; - Map _mapDataSource; - set mapDataSource(Map value) { - if (const MapEquality().equals(_mapDataSource, value)) { - return; - } - - _mapDataSource = value; - _refresh(); - markNeedsPaint(); - } - - MapShapeLayerDelegate get mapDelegate => _mapDelegate; - MapShapeLayerDelegate _mapDelegate; - set mapDelegate(MapShapeLayerDelegate value) { - if (_mapDelegate == value) { - return; - } - - if (_mapDelegate != null && - value != null && - _mapDelegate.shapeFile != value.shapeFile) { - _mapDelegate = value; - return; - } - - _mapDelegate = value; - _currentSelectedItem = null; - _prevSelectedItem = null; - _previousHoverItem = null; - _refresh(); - markNeedsPaint(); - _state.dataLabelAnimationController.value = 0.0; - _state.bubbleAnimationController.value = 0.0; - SchedulerBinding.instance.addPostFrameCallback(_initiateInitialAnimations); - } - - MapBubbleSettings get bubbleSettings => _bubbleSettings; - MapBubbleSettings _bubbleSettings; - set bubbleSettings(MapBubbleSettings value) { - if (_bubbleSettings == value) { - return; - } - if (_bubbleSettings.minRadius != value.minRadius || - _bubbleSettings.maxRadius != value.maxRadius) { - _bubbleSettings = value; - _mapDataSource.forEach((String key, _MapModel mapModel) { - _updateBubbleRadiusAndPath(mapModel); - }); - } else { - _bubbleSettings = value; - } - markNeedsPaint(); - } - - MapLegendSettings get legendSettings => _legendSettings; - MapLegendSettings _legendSettings; - set legendSettings(MapLegendSettings value) { - // Update [MapsShapeLayer.legendSettings] value only when - // [MapsShapeLayer.legend] property is set to shape. - if (_state.widget.legendSource != MapElement.shape || - _legendSettings == value) { - return; - } - _legendSettings = value; - if (_legendSettings.enableToggleInteraction) { - _initializeToggledShapeTweenColors(); - } - markNeedsPaint(); - } - - MapSelectionSettings get selectionSettings => _selectionSettings; - MapSelectionSettings _selectionSettings; - set selectionSettings(MapSelectionSettings value) { - if (_selectionSettings == value) { - return; - } - _selectionSettings = value; - } - - MapZoomPanBehavior get zoomPanBehavior => _zoomPanBehavior; - MapZoomPanBehavior _zoomPanBehavior; - set zoomPanBehavior(MapZoomPanBehavior value) { - if (_zoomPanBehavior == value) { - return; - } - _zoomPanBehavior = value; - } - - bool get enableShapeTooltip => _enableShapeTooltip; - bool _enableShapeTooltip; - set enableShapeTooltip(bool value) { - if (_enableShapeTooltip == value) { - return; - } - _enableShapeTooltip = value; - } - - bool get enableBubbleTooltip => _enableBubbleTooltip; - bool _enableBubbleTooltip; - set enableBubbleTooltip(bool value) { - if (_enableBubbleTooltip == value) { - return; - } - _enableBubbleTooltip = value; - } - - bool get enableSelection => _enableSelection; - bool _enableSelection; - set enableSelection(bool value) { - if (_enableSelection == value) { - return; - } - _enableSelection = value; - } - - SfMapsThemeData get themeData => _themeData; - SfMapsThemeData _themeData; - set themeData(SfMapsThemeData value) { - if (_themeData == value) { - return; - } - _themeData = value; - - if (_enableSelection) { - _initializeSelectionTweenColors(); - } - if (_legendSettings.enableToggleInteraction) { - _initializeToggledShapeTweenColors(); - } - - if (hasShapeHoverColor) { - _initializeHoverTweenColors(); - } - - markNeedsPaint(); - } - - @override - MouseCursor get cursor => defaultController.gesture == _Gesture.pan - ? SystemMouseCursors.grabbing - : SystemMouseCursors.basic; - - @override - PointerEnterEventListener get onEnter => null; - - @override - PointerHoverEventListener get onHover => _handleHover; - - @override - PointerExitEventListener get onExit => _handleExit; - - void _initializeSelectionTweenColors() { - final Color selectionColor = - _themeData.selectionColor.withOpacity(_selectionSettings.opacity); - _forwardSelectionColorTween.end = selectionColor; - _forwardSelectionStrokeColorTween.begin = _themeData.layerStrokeColor; - _forwardSelectionStrokeColorTween.end = _themeData.selectionStrokeColor; - - _reverseSelectionColorTween.begin = selectionColor; - _reverseSelectionStrokeColorTween.begin = _themeData.selectionStrokeColor; - _reverseSelectionStrokeColorTween.end = _themeData.layerStrokeColor; - _updateCurrentSelectedItemTween(); - } - - void _updateCurrentSelectedItemTween() { - if (_currentSelectedItem != null && - !defaultController.wasToggled(_currentSelectedItem)) { - _forwardSelectionColorTween.begin = - getActualShapeColor(_currentSelectedItem); - } - - if (_prevSelectedItem != null) { - _reverseSelectionColorTween.end = getActualShapeColor(_prevSelectedItem); - } - } - - void _initializeHoverTweenColors() { - final Color hoverStrokeColor = _getHoverStrokeColor(); - _forwardHoverStrokeColorTween.begin = _themeData.layerStrokeColor; - _forwardHoverStrokeColorTween.end = hoverStrokeColor; - _reverseHoverStrokeColorTween.begin = hoverStrokeColor; - _reverseHoverStrokeColorTween.end = _themeData.layerStrokeColor; - } - - Color _getHoverStrokeColor() { - final bool canAdjustHoverOpacity = - double.parse(_themeData.layerStrokeColor.opacity.toStringAsFixed(2)) != - _hoverColorOpacity; - return _themeData.shapeHoverStrokeColor != null && - _themeData.shapeHoverStrokeColor != Colors.transparent - ? _themeData.shapeHoverStrokeColor - : _themeData.layerStrokeColor.withOpacity( - canAdjustHoverOpacity ? _hoverColorOpacity : _minHoverOpacity); - } - - void _refresh([MapLatLng latlng]) { - if (hasSize && _mapDataSource != null && _mapDataSource.isNotEmpty) { - _computeActualFactor(); - defaultController.shapeLayerSizeFactor = _actualFactor; - if (_zoomPanBehavior != null) { - defaultController.shapeLayerSizeFactor *= _zoomPanBehavior.zoomLevel; - } - - defaultController.shapeLayerOffset = - _getTranslationPoint(defaultController.shapeLayerSizeFactor); - defaultController.shapeLayerOrigin = Offset.zero; - _adjustTranslationTo(latlng); - defaultController.updateVisibleBounds(); - _updateMapDataSourceForVisual(); - markNeedsPaint(); - } - } - - void _adjustTranslationTo(MapLatLng latlng) { - latlng ??= _zoomPanBehavior?.focalLatLng; - if (latlng != null) { - final Offset focalPoint = _pixelFromLatLng( - latlng.latitude, - latlng.longitude, - size, - defaultController.shapeLayerOffset, - defaultController.shapeLayerSizeFactor, - ); - final Offset center = - _getShapeBounds(defaultController.shapeLayerSizeFactor).center; - defaultController.shapeLayerOffset += - center + defaultController.shapeLayerOffset - focalPoint; - } - } - - void _computeActualFactor() { - final Offset minPoint = _pixelFromLatLng( - _state.shapeFileData.bounds.minLatitude, - _state.shapeFileData.bounds.minLongitude, - _size); - final Offset maxPoint = _pixelFromLatLng( - _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); - } - - Offset _getTranslationPoint(double factor, [Rect bounds]) { - assert(factor != null); - bounds ??= _getShapeBounds(factor); - // 0.0 is default translation value. - final double dx = _interpolateValue( - 0.0, _size.width - _actualShapeSize.width, -bounds.left); - final double dy = _interpolateValue( - 0.0, _size.height - _actualShapeSize.height, -bounds.top); - final Offset shift = Offset(_size.width - _actualShapeSize.width * factor, - _size.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); - final Offset maxPoint = _pixelFromLatLng( - _state.shapeFileData.bounds.maxLatitude, - _state.shapeFileData.bounds.maxLongitude, - _size, - translation, - factor); - return Rect.fromPoints(minPoint, maxPoint); - } - - void _updateMapDataSourceForVisual() { - if (_mapDataSource != null) { - Offset point; - Path shapePath; - dynamic coordinate; - List pixelPoints; - List rawPoints; - int rawPointsLength, pointsLength; - _mapDataSource.forEach((String key, _MapModel mapModel) { - double signedArea = 0.0, centerX = 0.0, centerY = 0.0; - rawPointsLength = mapModel.rawPoints.length; - mapModel.pixelPoints = List>(rawPointsLength); - shapePath = Path(); - for (int j = 0; j < rawPointsLength; j++) { - rawPoints = mapModel.rawPoints[j]; - pointsLength = rawPoints.length; - pixelPoints = mapModel.pixelPoints[j] = List(pointsLength); - for (int k = 0; k < pointsLength; k++) { - coordinate = rawPoints[k]; - point = pixelPoints[k] = _pixelFromLatLng( - coordinate[1], - coordinate[0], - _size, - defaultController.shapeLayerOffset, - defaultController.shapeLayerSizeFactor); - if (k == 0) { - shapePath.moveTo(point.dx, point.dy); - } else { - shapePath.lineTo(point.dx, point.dy); - final int l = k - 1; - if ((_state.widget.showDataLabels || _state.widget.showBubbles) && - l + 1 < pixelPoints.length) { - // Used mathematical formula to find - // the center of polygon points. - final double x0 = pixelPoints[l].dx, y0 = pixelPoints[l].dy; - final double x1 = pixelPoints[l + 1].dx, - y1 = pixelPoints[l + 1].dy; - signedArea += (x0 * y1) - (y0 * x1); - centerX += (x0 + x1) * (x0 * y1 - x1 * y0); - centerY += (y0 + y1) * (x0 * y1 - x1 * y0); - } - } - } - shapePath.close(); - } - - mapModel.shapePath = shapePath; - _findPathCenterAndWidth(signedArea, centerX, centerY, mapModel); - _updateBubbleRadiusAndPath(mapModel); - }); - } - } - - void _findPathCenterAndWidth( - double signedArea, double centerX, double centerY, _MapModel mapModel) { - if (_state.widget.showDataLabels || _state.widget.showBubbles) { - // 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) { - if (bubbleSizeValue == _state.minBubbleValue) { - mapModel.bubbleRadius = _bubbleSettings.minRadius; - } else if (bubbleSizeValue == _state.maxBubbleValue) { - mapModel.bubbleRadius = _bubbleSettings.maxRadius; - } else { - final double percentage = ((bubbleSizeValue - _state.minBubbleValue) / - (_state.maxBubbleValue - _state.minBubbleValue)) * - 100; - mapModel.bubbleRadius = bubbleSettings.minRadius + - (bubbleSettings.maxRadius - bubbleSettings.minRadius) * - (percentage / 100); - } - } - - if ((_enableBubbleTooltip || hasBubbleHoverColor) && - mapModel.bubbleRadius != null) { - mapModel.bubblePath = Path() - ..addOval( - Rect.fromCircle( - center: mapModel.shapePathCenter, - radius: mapModel.bubbleRadius, - ), - ); - } - } - - // Invoking animation for data label and bubble. - void _initiateInitialAnimations(Duration timeStamp) { - if (_state.mounted) { - if (_state.widget.showBubbles) { - _state.bubbleAnimationController.forward(from: 0); - } else if (_state.widget.showDataLabels) { - _state.dataLabelAnimationController.forward(from: 0); - } - } - } - - void _handleScaleStart(ScaleStartDetails details) { - if (canZoom) { - if (defaultController.gesture == _Gesture.scale) { - _zoomEnd(); - } - - defaultController.isInInteractive = true; - defaultController.gesture = null; - _downGlobalPoint = details.focalPoint; - _downLocalPoint = details.localFocalPoint; - _refVisibleBounds = defaultController - .getVisibleBounds(defaultController.shapeLayerOffset); - _refShapeBounds = _getShapeBounds(defaultController.shapeLayerSizeFactor, - defaultController.shapeLayerOffset); - final MapLatLngBounds newVisibleBounds = - defaultController.getVisibleLatLngBounds( - _refVisibleBounds.topRight, _refVisibleBounds.bottomLeft); - _zoomDetails = MapZoomDetails( - newVisibleBounds: newVisibleBounds, - ); - _panDetails = MapPanDetails( - newVisibleBounds: newVisibleBounds, - ); - } - } - - // Scale and pan are handled in scale gesture. - void _handleScaleUpdate(ScaleUpdateDetails details) { - defaultController.isInInteractive = true; - defaultController.gesture ??= - _getGestureType(details.scale, details.localFocalPoint); - if (!canZoom || defaultController.gesture == null) { - return; - } - - switch (defaultController.gesture) { - case _Gesture.scale: - _singleTapConfirmed = false; - if (_zoomPanBehavior.enablePinching && - defaultController.shapeLayerSizeFactor * details.scale >= - _actualFactor) { - _invokeOnZooming(details.scale, _downLocalPoint, _downGlobalPoint); - } - return; - case _Gesture.pan: - _singleTapConfirmed = false; - if (_zoomPanBehavior.enablePanning) { - final Offset delta = - _getValidPanDelta(details.localFocalPoint - _downLocalPoint); - final Rect visibleBounds = defaultController - .getVisibleBounds(defaultController.shapeLayerOffset + delta); - _panDetails = MapPanDetails( - globalFocalPoint: details.focalPoint, - localFocalPoint: details.localFocalPoint, - zoomLevel: _zoomPanBehavior.zoomLevel, - delta: delta, - previousVisibleBounds: _panDetails != null - ? _panDetails.newVisibleBounds - : defaultController.visibleLatLngBounds, - newVisibleBounds: defaultController.getVisibleLatLngBounds( - visibleBounds.topRight, - visibleBounds.bottomLeft, - defaultController.shapeLayerOffset + delta), - ); - if (_state.widget.onWillPan == null || - _state.widget.onWillPan(_panDetails)) { - _zoomPanBehavior?.onPanning(_panDetails); - } - } - return; - } - } - - void _invokeOnZooming(double scale, - [Offset localFocalPoint, Offset globalFocalPoint]) { - final double newZoomLevel = _getZoomLevel(scale); - final double newShapeLayerSizeFactor = _getScale(newZoomLevel); - final Offset newShapeLayerOffset = - defaultController.getZoomingTranslation(origin: localFocalPoint); - final Rect newVisibleBounds = defaultController.getVisibleBounds( - newShapeLayerOffset, newShapeLayerSizeFactor); - _zoomDetails = MapZoomDetails( - localFocalPoint: localFocalPoint, - globalFocalPoint: globalFocalPoint, - previousZoomLevel: _zoomPanBehavior.zoomLevel, - newZoomLevel: newZoomLevel, - previousVisibleBounds: _zoomDetails != null - ? _zoomDetails.newVisibleBounds - : defaultController.visibleLatLngBounds, - newVisibleBounds: defaultController.getVisibleLatLngBounds( - newVisibleBounds.topRight, - newVisibleBounds.bottomLeft, - newShapeLayerOffset, - newShapeLayerSizeFactor, - ), - ); - if (_state.widget.onWillZoom == null || - _state.widget.onWillZoom(_zoomDetails)) { - _zoomPanBehavior?.onZooming(_zoomDetails); - } - } - - Offset _getValidPanDelta(Offset delta) { - final Rect currentShapeBounds = _getShapeBounds( - defaultController.shapeLayerSizeFactor, - defaultController.shapeLayerOffset + delta); - double dx = 0.0, dy = 0.0; - if (_refVisibleBounds.width < _refShapeBounds.width) { - dx = delta.dx; - if (currentShapeBounds.left > _refVisibleBounds.left) { - dx = _refVisibleBounds.left - _refShapeBounds.left; - } - - if (currentShapeBounds.right < _refVisibleBounds.right) { - dx = _refVisibleBounds.right - _refShapeBounds.right; - } - } - - if (_refVisibleBounds.height < _refShapeBounds.height) { - dy = delta.dy; - if (currentShapeBounds.top > _refVisibleBounds.top) { - dy = _refVisibleBounds.top - _refShapeBounds.top; - } - - if (currentShapeBounds.bottom < _refVisibleBounds.bottom) { - dy = _refVisibleBounds.bottom - _refShapeBounds.bottom; - } - } - - return Offset(dx, dy); - } - - _Gesture _getGestureType(double scale, Offset point) { - if (scale == 1) { - if (_downLocalPoint != null) { - final Offset distance = point - _downLocalPoint; - return distance.dx.abs() > _minPanDistance || - distance.dy.abs() > _minPanDistance - ? _Gesture.pan - : null; - } - } - - return _Gesture.scale; - } - - Offset _getAdjTranslation(double zoomLevel) { - double dx = 0.0, dy = 0.0; - final Rect currentBounds = defaultController.currentBounds; - if (currentBounds.left > paintBounds.left) { - dx = paintBounds.left - currentBounds.left; - } - - if (currentBounds.right < paintBounds.right) { - dx = paintBounds.right - currentBounds.right; - } - - if (currentBounds.top > paintBounds.top) { - dy = paintBounds.top - currentBounds.top; - } - - if (currentBounds.bottom < paintBounds.bottom) { - dy = paintBounds.bottom - currentBounds.bottom; - } - - return Offset(dx, dy); - } - - void _validateEdges(double zoomLevel, [Offset origin]) { - final Offset leftTop = defaultController.getZoomingTranslation( - origin: origin, - scale: _getScale(zoomLevel), - previousOrigin: defaultController.shapeLayerOrigin); - defaultController.currentBounds = Rect.fromLTWH(leftTop.dx, leftTop.dy, - _size.width * zoomLevel, _size.height * zoomLevel); - defaultController.adjustment = _getAdjTranslation(zoomLevel); - } - - void _handleZooming(MapZoomDetails details) { - if (defaultController.isInInteractive && details.localFocalPoint != null) { - // Updating map while pinching and scrolling. - defaultController.localScale = _getScale(details.newZoomLevel); - defaultController.pinchCenter = details.localFocalPoint; - defaultController.updateVisibleBounds( - defaultController.getZoomingTranslation() + - defaultController.adjustment, - defaultController.shapeLayerSizeFactor * - defaultController.localScale); - _validateEdges(details.newZoomLevel); - } else { - // Updating map via toolbar. - _downLocalPoint = null; - _downGlobalPoint = null; - _validateEdges( - details.newZoomLevel, Offset(_size.width / 2, _size.height / 2)); - defaultController.shapeLayerOrigin = - defaultController.getZoomingTranslation( - origin: Offset(_size.width / 2, _size.height / 2), - scale: _getScale(details.newZoomLevel), - previousOrigin: defaultController.shapeLayerOrigin) + - defaultController.adjustment; - defaultController.shapeLayerSizeFactor = - _actualFactor * details.newZoomLevel; - defaultController.shapeLayerOffset = - _getTranslationPoint(defaultController.shapeLayerSizeFactor) + - defaultController.adjustment; - if (details.newZoomLevel != 1) { - _adjustTranslationTo(defaultController.visibleFocalLatLng); - } - defaultController.updateVisibleBounds(); - _updateMapDataSourceForVisual(); - } - _zoomPanBehavior.zoomLevel = details.newZoomLevel; - } - - void _handlePanning(MapPanDetails details) { - if (_currentHoverItem != null) { - _previousHoverItem = _currentHoverItem; - _currentHoverItem = null; - } - defaultController.panDistance = details.delta; - defaultController.updateVisibleBounds( - defaultController.shapeLayerOffset + details.delta); - markNeedsPaint(); - } - - void _handleScaleEnd(ScaleEndDetails details) { - if (defaultController.gesture == null) { - return; - } - - switch (defaultController.gesture) { - case _Gesture.scale: - _zoomEnd(); - break; - case _Gesture.pan: - _panEnd(); - break; - } - - defaultController.gesture = null; - } - - void _zoomEnd() { - defaultController.isInInteractive = false; - _zoomingDelayTimer?.cancel(); - _zoomingDelayTimer = null; - _zoomDetails = null; - _panDetails = null; - if (_zoomPanBehavior != null && _zoomPanBehavior.enablePinching) { - defaultController.shapeLayerOffset = - defaultController.getZoomingTranslation() + - defaultController.adjustment; - defaultController.shapeLayerOrigin = - defaultController.getZoomingTranslation( - previousOrigin: defaultController.shapeLayerOrigin) + - defaultController.adjustment; - defaultController.shapeLayerSizeFactor *= defaultController.localScale; - _updateMapDataSourceForVisual(); - _invalidateChildren(); - markNeedsPaint(); - } - - _downLocalPoint = null; - _downGlobalPoint = null; - defaultController.gesture = null; - defaultController.adjustment = Offset.zero; - defaultController.localScale = 1.0; - } - - void _panEnd() { - defaultController.isInInteractive = false; - _zoomDetails = null; - _panDetails = null; - if (_zoomPanBehavior.enablePanning) { - defaultController.shapeLayerOffset += defaultController.panDistance; - defaultController.shapeLayerOrigin += defaultController.panDistance; - _updateMapDataSourceForVisual(); - _invalidateChildren(); - markNeedsPaint(); - } - - _downLocalPoint = null; - _downGlobalPoint = null; - defaultController.gesture = null; - defaultController.panDistance = Offset.zero; - } - - void _invalidateChildren() { - RenderBox child = firstChild; - while (child != null) { - if (child is _ShapeLayerChildRenderBoxBase) { - child.refresh(); - } - - final StackParentData childParentData = child.parentData; - child = childParentData.nextSibling; - } - } - - /// Handling zooming using mouse wheel scrolling. - void _handleScrollEvent(PointerScrollEvent event) { - if (_zoomPanBehavior != null && _zoomPanBehavior.enablePinching) { - defaultController.isInInteractive = true; - defaultController.gesture ??= _Gesture.scale; - if (defaultController.gesture != _Gesture.scale) { - return; - } - - if (_currentHoverItem != null) { - _previousHoverItem = _currentHoverItem; - _currentHoverItem = null; - } - _downGlobalPoint ??= event.position; - _downLocalPoint ??= event.localPosition; - double scale = defaultController.localScale - (event.scrollDelta.dy / 60); - if (defaultController.shapeLayerSizeFactor * scale < _actualFactor) { - scale = _actualFactor / defaultController.shapeLayerSizeFactor; - } - - _invokeOnZooming(scale, _downLocalPoint, _downGlobalPoint); - // When the user didn't scrolled or scaled for certain time period, - // we will refresh the map to the corresponding zoom level. - _zoomingDelayTimer?.cancel(); - _zoomingDelayTimer = Timer(const Duration(milliseconds: 250), () { - _zoomEnd(); - }); - } - } - - void _handleZoomLevelChange(double zoomLevel, {MapLatLng latlng}) { - if (defaultController.isInInteractive) { - markNeedsPaint(); - } else { - if (latlng != null) { - defaultController.visibleFocalLatLng = latlng; - } - _invokeOnZooming(_getScale(zoomLevel)); - } - } - - void _handlePanTo(MapLatLng latlng) { - defaultController.visibleFocalLatLng = latlng; - _refresh(latlng); - if (defaultController.gesture == null) { - _invalidateChildren(); - } - } - - void _handleReset() { - defaultController.shapeLayerSizeFactor = _actualFactor; - defaultController.shapeLayerOffset = - _getTranslationPoint(defaultController.shapeLayerSizeFactor); - _updateMapDataSourceForVisual(); - markNeedsPaint(); - } - - void _handleExit(PointerExitEvent event) { - if (_state.widget.showBubbles && hasBubbleHoverColor) { - RenderBox child = lastChild; - while (child != null) { - final StackParentData childParentData = child.parentData; - if (child is _RenderMapBubble) { - child.onExit(); - } - - child = childParentData.previousSibling; - } - } - - if (hasShapeHoverColor && _currentHoverItem != null) { - _previousHoverItem = _currentHoverItem; - _currentHoverItem = null; - markNeedsPaint(); - } - } - - double _getZoomLevel(double scale) { - return _interpolateValue( - defaultController.shapeLayerSizeFactor * scale / _actualFactor, - _zoomPanBehavior.minZoomLevel, - _zoomPanBehavior.maxZoomLevel, - ); - } - - double _getScale(double zoomLevel) { - return _actualFactor * zoomLevel / defaultController.shapeLayerSizeFactor; - } - - void _handleTapUp(Offset localPosition) { - _handleTapUpAndHover(localPosition); - if (_currentSelectedItem != null) { - _currentHoverItem = null; - } - } - - void _handleHover(PointerHoverEvent event) { - final RenderBox renderBox = context.findRenderObject(); - final Offset localPosition = renderBox.globalToLocal(event.position); - _handleTapUpAndHover(localPosition, isHover: true); - } - - void _handleTapUpAndHover(Offset position, {bool isHover = false}) { - if (_mapDataSource == null || _mapDataSource.isEmpty) { - return; - } - - double bubbleRadius; - _MapModel model; - _Layer layer; - for (final _MapModel mapModel in _mapDataSource.values) { - final bool wasToggled = defaultController.wasToggled(mapModel); - if (_isBubbleContains(position, mapModel)) { - layer = _Layer.bubble; - if (!wasToggled && - (bubbleRadius == null || mapModel.bubbleRadius < bubbleRadius)) { - bubbleRadius = mapModel.bubbleRadius; - model = mapModel; - } - } else if (_isShapeContains(position, mapModel, layer) && - !(wasToggled && _state.widget.legendSource == MapElement.shape)) { - model = mapModel; - layer = _Layer.shape; - if (!(_enableBubbleTooltip || hasBubbleHoverColor)) { - break; - } - } - } - - if (isHover) { - _prevSelectedItem = null; - _performChildHover(position, model, layer); - } else { - _setCurrentSelectedItem(model); - _performChildTap(model, position, layer); - } - } - - bool _isBubbleContains(Offset position, _MapModel mapModel) { - return (_enableBubbleTooltip || hasBubbleHoverColor) && - mapModel.bubblePath != null && - mapModel.bubblePath.contains(position); - } - - bool _isShapeContains( - Offset position, _MapModel mapModel, _Layer interactPath) { - return (_enableSelection || _enableShapeTooltip || hasShapeHoverColor) && - interactPath != _Layer.bubble && - mapModel.shapePath.contains(position); - } - - void _setCurrentSelectedItem(_MapModel mapModel) { - if (_enableSelection && mapModel != null && mapModel.dataIndex != null) { - // Update the previously selected shape details to the - // [_prevSelectedItem] field, before updating the current selected - // shape details into the [_currentSelectedItem] field. - _prevSelectedItem = _currentSelectedItem; - if (_state.widget.controller != null) { - _state.widget.controller.selectedIndex = - _state.widget.controller.selectedIndex == mapModel.dataIndex - ? -1 - : mapModel.dataIndex; - } else { - _currentSelectedItem = mapModel; - _updateSelectedItemModel(); - } - - _updateCurrentSelectedItemTween(); - _state.selectionAnimationController.forward(from: 0); - } - } - - void _updateSelectedItemModel() { - _currentSelectedItem.isSelected = !_currentSelectedItem.isSelected; - if (_prevSelectedItem != null && - _currentSelectedItem.dataIndex != _prevSelectedItem.dataIndex) { - _prevSelectedItem.isSelected = false; - } - - if (_prevSelectedItem != null && - _currentSelectedItem.dataIndex == _prevSelectedItem.dataIndex) { - _currentSelectedItem = null; - } - SchedulerBinding.instance.addPostFrameCallback(_handleSelectionChanged); - markNeedsPaint(); - } - - void _handleSelectionChanged(Duration time) { - if (_state.widget.onSelectionChanged != null) { - _state.widget.onSelectionChanged( - _currentSelectedItem != null ? _currentSelectedItem.dataIndex : -1); - } - } - - void _performChildTap(_MapModel model, Offset position, _Layer interactPath) { - if ((_enableShapeTooltip || _enableBubbleTooltip) && model != null) { - RenderBox child = firstChild; - while (child != null) { - final StackParentData childParentData = child.parentData; - if (child is _RenderMapTooltip) { - child.onTap(position, model: model, layer: interactPath); - break; - } - child = childParentData.nextSibling; - } - } - } - - void _performChildHover(Offset position, _MapModel model, _Layer layer) { - RenderBox child = firstChild; - while (child != null) { - final StackParentData childParentData = child.parentData; - if (child is _RenderMapTooltip) { - child.onHover(position, model: model, layer: layer); - } else if (hasBubbleHoverColor) { - if (child is _RenderMapBubble) { - child.onHover(position, model: model, layer: layer); - } - } - child = childParentData.nextSibling; - } - - if (hasShapeHoverColor && - (_currentSelectedItem == null || _currentSelectedItem != model)) { - if (layer == _Layer.shape && _currentHoverItem != model) { - _previousHoverItem = _currentHoverItem; - _currentHoverItem = model; - _updateHoverItemTween(); - } else if ((_currentHoverItem != null && _currentHoverItem != model) || - (layer == _Layer.bubble && _currentHoverItem == model)) { - _previousHoverItem = _currentHoverItem; - _currentHoverItem = null; - _updateHoverItemTween(); - } - } - } - - void _updateHoverItemTween() { - if (_currentHoverItem != null) { - _forwardHoverColorTween.begin = getActualShapeColor(_currentHoverItem); - _forwardHoverColorTween.end = _getHoverFillColor(_currentHoverItem); - } - - if (_previousHoverItem != null) { - _reverseHoverColorTween.begin = _getHoverFillColor(_previousHoverItem); - _reverseHoverColorTween.end = getActualShapeColor(_previousHoverItem); - } - - _state.hoverShapeAnimationController.forward(from: 0); - } - - Color _getHoverFillColor(_MapModel model) { - final bool canAdjustHoverOpacity = - double.parse(getActualShapeColor(model).opacity.toStringAsFixed(2)) != - _hoverColorOpacity; - return _themeData.shapeHoverColor != null && - _themeData.shapeHoverColor != Colors.transparent - ? _themeData.shapeHoverColor - : getActualShapeColor(model).withOpacity( - canAdjustHoverOpacity ? _hoverColorOpacity : _minHoverOpacity); - } - - void _handleShapeLayerControllerState() { - if (_enableSelection) { - final int selectedIndex = _state.widget.controller.selectedIndex; - assert(selectedIndex < _mapDelegate.dataCount); - _prevSelectedItem = _currentSelectedItem; - _updateCurrentSelectedItemTween(); - _state.selectionAnimationController.forward(from: 0); - if (selectedIndex == -1) { - if (_prevSelectedItem != null) { - _prevSelectedItem.isSelected = false; - } - - if (_currentSelectedItem != null) { - _currentSelectedItem = null; - } - - SchedulerBinding.instance.addPostFrameCallback(_handleSelectionChanged); - markNeedsPaint(); - return; - } - _currentSelectedItem = _mapDataSource.values.firstWhere( - (_MapModel element) => element.dataIndex == selectedIndex); - _updateSelectedItemModel(); - } - } - - void _initializeToggledShapeTweenColors() { - final Color toggledShapeColor = - _themeData.toggledItemColor != Colors.transparent - ? _themeData.toggledItemColor - .withOpacity(_legendSettings.toggledItemOpacity) - : null; - - _forwardToggledShapeColorTween.end = toggledShapeColor; - _forwardToggledShapeStrokeColorTween.begin = _themeData.layerStrokeColor; - _forwardToggledShapeStrokeColorTween.end = - _themeData.toggledItemStrokeColor != Colors.transparent - ? _themeData.toggledItemStrokeColor - : null; - - _reverseToggledShapeColorTween.begin = toggledShapeColor; - _reverseToggledShapeStrokeColorTween.begin = - _themeData.toggledItemStrokeColor != Colors.transparent - ? _themeData.toggledItemStrokeColor - : null; - _reverseToggledShapeStrokeColorTween.end = _themeData.layerStrokeColor; - } - - void _handleToggleChange() { - _previousHoverItem = null; - if (_state.widget.legendSource == MapElement.shape) { - _MapModel model; - if (_state.widget.delegate.shapeColorMappers == null) { - model = mapDataSource.values - .elementAt(defaultController.currentToggledItemIndex); - } else { - for (final mapModel in _mapDataSource.values) { - if (mapModel.dataIndex != null && - mapModel.legendMapperIndex == - defaultController.currentToggledItemIndex) { - model = mapModel; - break; - } - } - } - - final Color shapeColor = (_enableSelection && - _currentSelectedItem != null && - _currentSelectedItem.actualIndex == model.actualIndex) - ? _themeData.selectionColor.withOpacity(_selectionSettings.opacity) - : getActualShapeColor(model); - _forwardToggledShapeColorTween.begin = shapeColor; - _reverseToggledShapeColorTween.end = shapeColor; - _state.toggleAnimationController.forward(from: 0); - } - } - - @override - void attach(PipelineOwner owner) { - super.attach(owner); - _state.widget.controller?.addListener(_handleShapeLayerControllerState); - _state.selectionAnimationController?.addListener(markNeedsPaint); - _state.toggleAnimationController?.addListener(markNeedsPaint); - _state.hoverShapeAnimationController?.addListener(markNeedsPaint); - if (defaultController != null) { - defaultController.addToggleListener(_handleToggleChange); - defaultController.addZoomingListener(_handleZooming); - defaultController.addPanningListener(_handlePanning); - defaultController.addResetListener(_handleReset); - } - SchedulerBinding.instance.addPostFrameCallback(_initiateInitialAnimations); - } - - @override - void detach() { - _state.dataLabelAnimationController.value = 0.0; - _state.bubbleAnimationController.value = 0.0; - _state.widget.controller?.removeListener(_handleShapeLayerControllerState); - _state.selectionAnimationController?.removeListener(markNeedsPaint); - _state.toggleAnimationController?.removeListener(markNeedsPaint); - _state.hoverShapeAnimationController?.removeListener(markNeedsPaint); - if (defaultController != null) { - defaultController.removeToggleListener(_handleToggleChange); - defaultController.removeZoomingListener(_handleZooming); - defaultController.removePanningListener(_handlePanning); - defaultController.removeResetListener(_handleReset); - } - _zoomingDelayTimer?.cancel(); - super.detach(); - } - - @override - bool hitTestSelf(Offset position) => isInteractive; - - @override - void handleEvent(PointerEvent event, HitTestEntry entry) { - _zoomPanBehavior?.handleEvent(event, entry); - if (isInteractive && event.down && event is PointerDownEvent) { - _pointerCount++; - _scaleGestureRecognizer.addPointer(event); - _singleTapConfirmed = _pointerCount == 1; - } else if (event is PointerUpEvent || event is PointerCancelEvent) { - if (_singleTapConfirmed) { - _handleTapUp(event.localPosition); - _downLocalPoint = null; - _downGlobalPoint = null; - } - - _pointerCount = 0; - } else if (event is PointerScrollEvent) { - _handleScrollEvent(event); - } - } - - @override - void performLayout() { - _size = _getBoxSize(constraints); - defaultController.shapeLayerBoxSize = _size; - if (!hasSize || size != _size) { - size = _size; - _refresh(defaultController.visibleFocalLatLng); - } - - final BoxConstraints looseConstraints = BoxConstraints.loose(_size); - RenderBox child = firstChild; - while (child != null) { - final StackParentData childParentData = child.parentData; - child.layout(looseConstraints, parentUsesSize: true); - child = childParentData.nextSibling; - } - } - - @override - bool get isRepaintBoundary => true; - - @override - void paint(PaintingContext context, Offset offset) { - if (_mapDataSource != null && _mapDataSource.isNotEmpty) { - context.canvas - ..save() - ..clipRect(offset & _size); - defaultController.applyTransform(context, offset); - final bool hasToggledIndices = - defaultController.toggledIndices.isNotEmpty; - final Paint fillPaint = Paint()..isAntiAlias = true; - final Paint strokePaint = Paint() - ..isAntiAlias = true - ..style = PaintingStyle.stroke; - final bool hasPrevSelectedItem = _prevSelectedItem != null && - !defaultController.wasToggled(_prevSelectedItem); - - _mapDataSource.forEach((String key, _MapModel model) { - if (_currentHoverItem != null && - _currentHoverItem.primaryKey == model.primaryKey) { - return; - } - - if (hasPrevSelectedItem && _prevSelectedItem.primaryKey == key) { - fillPaint.color = - _reverseSelectionColorTween.evaluate(_selectionColorAnimation); - strokePaint - ..color = _reverseSelectionStrokeColorTween - .evaluate(_selectionColorAnimation) - ..strokeWidth = _themeData.selectionStrokeWidth; - } else if (_previousHoverItem != null && - _previousHoverItem.primaryKey == key && - !defaultController.wasToggled(_previousHoverItem) && - _previousHoverItem != _currentHoverItem) { - fillPaint.color = _themeData.shapeHoverColor != Colors.transparent - ? _reverseHoverColorTween.evaluate(_hoverColorAnimation) - : getActualShapeColor(model); - - if (_themeData.shapeHoverStrokeWidth > 0.0 && - _themeData.shapeHoverStrokeColor != Colors.transparent) { - strokePaint - ..color = - _reverseHoverStrokeColorTween.evaluate(_hoverColorAnimation) - ..strokeWidth = _themeData.layerStrokeWidth; - } else { - strokePaint - ..color = _themeData.layerStrokeColor - ..strokeWidth = _themeData.layerStrokeWidth; - } - } else { - _updateFillColor(model, fillPaint, hasToggledIndices); - _updateStrokePaint(model, strokePaint, hasToggledIndices); - } - - context.canvas.drawPath(model.shapePath, fillPaint); - if (strokePaint.strokeWidth > 0.0 && - strokePaint.color != Colors.transparent) { - strokePaint.strokeWidth = - _getIntrinsicStrokeWidth(strokePaint.strokeWidth); - context.canvas.drawPath(model.shapePath, strokePaint); - } - }); - - _drawHoverShape(context, fillPaint, strokePaint); - _drawSelectedShape(context, fillPaint, strokePaint); - context.canvas.restore(); - super.paint(context, offset); - } - } - - // Returns the color to the shape based on the [shapeColorMappers], - // [palette], and [layerColor] properties. - Color getActualShapeColor(_MapModel model) { - return model.shapeColor ?? - (_state.paletteLength > 0 - ? _state.widget.palette[model.actualIndex % _state.paletteLength] - : _themeData.layerColor); - } - - double _getIntrinsicStrokeWidth(double strokeWidth) { - return strokeWidth /= defaultController.gesture == _Gesture.scale - ? defaultController.localScale - : 1; - } - - // Set the color to the toggled and un-toggled shapes based on - // the [legendController.toggledIndices] collection. - void _updateFillColor( - _MapModel model, Paint fillPaint, bool hasToggledIndices) { - fillPaint.style = PaintingStyle.fill; - if (_state.widget.legendSource == MapElement.shape) { - if (defaultController.currentToggledItemIndex == - model.legendMapperIndex) { - final Color shapeColor = defaultController.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 - // un-toggled, then the [_reverseToggledShapeColorTween] return. - fillPaint.color = shapeColor ?? Colors.transparent; - return; - } else if (hasToggledIndices && defaultController.wasToggled(model)) { - // Set toggled color to the previously toggled shapes. - fillPaint.color = - _forwardToggledShapeColorTween.end ?? Colors.transparent; - return; - } - } - - fillPaint.color = getActualShapeColor(model); - } - - // 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) { - if (_state.widget.legendSource == MapElement.shape) { - if (defaultController.currentToggledItemIndex == - model.legendMapperIndex) { - final Color shapeStrokeColor = defaultController.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 = defaultController.wasToggled(model) - ? _legendSettings.toggledItemStrokeWidth - : _themeData.layerStrokeWidth; - return; - } else if (hasToggledIndices && defaultController.wasToggled(model)) { - // Set toggled stroke color to the previously toggled shapes. - strokePaint - ..color = - _forwardToggledShapeStrokeColorTween.end ?? Colors.transparent - ..strokeWidth = _legendSettings.toggledItemStrokeWidth; - return; - } - } - - strokePaint - ..color = _themeData.layerStrokeColor - ..strokeWidth = _themeData.layerStrokeWidth; - } - - void _drawSelectedShape( - PaintingContext context, Paint fillPaint, Paint strokePaint) { - if (_currentSelectedItem != null && - !defaultController.wasToggled(_currentSelectedItem)) { - fillPaint.color = - _forwardSelectionColorTween.evaluate(_selectionColorAnimation); - context.canvas.drawPath(_currentSelectedItem.shapePath, fillPaint); - if (_themeData.selectionStrokeWidth > 0.0) { - strokePaint - ..color = _forwardSelectionStrokeColorTween - .evaluate(_selectionColorAnimation) - ..strokeWidth = - _getIntrinsicStrokeWidth(_themeData.selectionStrokeWidth); - context.canvas.drawPath(_currentSelectedItem.shapePath, strokePaint); - } - } - } - - void _drawHoverShape( - PaintingContext context, Paint fillPaint, Paint strokePaint) { - if (_currentHoverItem != null) { - fillPaint.color = _themeData.shapeHoverColor != Colors.transparent - ? _forwardHoverColorTween.evaluate(_hoverColorAnimation) - : getActualShapeColor(_currentHoverItem); - context.canvas.drawPath(_currentHoverItem.shapePath, fillPaint); - if (_themeData.shapeHoverStrokeWidth > 0.0 && - _themeData.shapeHoverStrokeColor != Colors.transparent) { - strokePaint - ..color = _forwardHoverStrokeColorTween.evaluate(_hoverColorAnimation) - ..strokeWidth = - _getIntrinsicStrokeWidth(_themeData.shapeHoverStrokeWidth); - } else { - strokePaint - ..color = _themeData.layerStrokeColor - ..strokeWidth = _getIntrinsicStrokeWidth(_themeData.layerStrokeWidth); - } - - if (strokePaint.strokeWidth > 0.0 && - strokePaint.color != Colors.transparent) { - context.canvas.drawPath(_currentHoverItem.shapePath, strokePaint); - } - } - } -} diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/maps_tile_layer.dart b/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart similarity index 54% rename from packages/syncfusion_flutter_maps/lib/src/layer/maps_tile_layer.dart rename to packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart index 3df2664ae..608f128ec 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/maps_tile_layer.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart @@ -1,10 +1,155 @@ -part of maps; +import 'dart:async'; +import 'dart:convert'; +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/physics.dart'; +import 'package:flutter/rendering.dart'; +import 'package:http/http.dart' as http; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../behavior/zoom_pan_behavior.dart'; +import '../controller/default_controller.dart'; +import '../controller/shape_layer_controller.dart'; +import '../elements/marker.dart'; +import '../elements/toolbar.dart'; +import '../elements/tooltip.dart'; +import '../layer/layer_base.dart'; +import '../layer/shape_layer.dart'; +import '../layer/vector_layers.dart'; +import '../settings.dart'; +import '../utils.dart'; + +Offset _pixelFromLatLng(MapLatLng latLng, double scale) { + final double latitude = + _clip(latLng.latitude, minimumLatitude, maximumLatitude); + final double longitude = + _clip(latLng.longitude, minimumLongitude, maximumLongitude); + final double x = (longitude + 180) / 360; + final double sinLatitude = sin(latitude * pi / 180); + final double y = 0.5 - log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * pi); + + final double tileSize = getTotalTileWidth(scale); + final double pixelX = _clip(x * tileSize + 0.5, 0, tileSize - 1); + final double pixelY = _clip(y * tileSize + 0.5, 0, tileSize - 1); + return Offset(pixelX, pixelY); +} + +MapLatLng _latLngFromPixel(Offset point, {double scale}) { + final double tileSize = getTotalTileWidth(scale); + final double x = (_clip(point.dx, 0, tileSize - 1) / tileSize) - 0.5; + final double y = 0.5 - (_clip(point.dy, 0, tileSize - 1) / tileSize); + + final double latitude = 90 - 360 * atan(exp(-y * 2 * pi)) / pi; + final double longitude = 360 * x; + return MapLatLng(latitude, longitude); +} + +double _clip(double value, double minValue, double maxValue) { + return min(max(value, minValue), maxValue); +} + +/// Returns the URL template in the required format for the Bing Maps. +/// +/// For Bing Maps, an additional step is required. The format of the required +/// URL varies from the other tile services. Hence, we have added this top-level +/// function which returns the URL in the required format. +/// +/// You can use the URL template returned from this function to set it to the +/// [MapTileLayer.urlTemplate] property. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return FutureBuilder( +/// future: getBingUrlTemplate( +/// 'http://dev.virtualearth.net/REST/V1/Imagery/Metadata/Road +/// OnDemand?output=json&include=ImageryProviders&key=YOUR_KEY'), +/// builder: (context, snapshot) { +/// if (snapshot.hasData) { +/// return SfMaps( +/// layers: [ +/// MapTileLayer( +/// initialFocalLatLng: MapLatLng(20.5937, 78.9629), +/// zoomPanBehavior: MapZoomPanBehavior(), +/// initialZoomLevel: 3, +/// urlTemplate: snapshot.data, +/// ), +/// ], +/// ); +/// } +/// return CircularProgressIndicator(); +/// }); +/// } +/// ``` +Future getBingUrlTemplate(String url) async { + final http.Response response = await _fetchResponse(url); + assert(response.statusCode == 200, 'Invalid key'); + if (response.statusCode == 200) { + final Map decodedJson = json.decode(response.body); + String imageUrl; + String imageUrlSubDomains; + if (decodedJson['authenticationResultCode'] == 'ValidCredentials') { + for (final String key in decodedJson.keys) { + if (key == 'resourceSets') { + final List resourceSets = decodedJson[key]; + for (final key in resourceSets[0].keys) { + if (key == 'resources') { + final List resources = (resourceSets[0])[key]; + final Map resourcesMap = resources[0]; + imageUrl = resourcesMap['imageUrl']; + final List subDomains = + resourcesMap['imageUrlSubdomains']; + imageUrlSubDomains = subDomains[0]; + break; + } + } + break; + } + } + + final List splitUrl = imageUrl.split('{subdomain}'); + return splitUrl[0] + imageUrlSubDomains + splitUrl[1]; + } + } + return null; +} + +Future _fetchResponse(String url) { + return http.get(url); +} + +/// Provides options for adding, removing, deleting, updating markers +/// collection and converting pixel points to latitude and longitude. +class MapTileLayerController extends MapLayerController { + /// Instance of _MapTileLayerState. + _MapTileLayerState _state; + + /// Returns the current markers count. + int get markersCount => _markersCount; + int _markersCount = 0; + + /// 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); + final Offset newCenterPoint = + actualCenterPixelPosition - localPointCenterDiff; + return _latLngFromPixel(newCenterPoint, scale: _state._currentZoomLevel); + } +} /// Tile layer which renders the tiles returned from the Web Map Tile /// Services (WMTS) like OpenStreetMap, Bing Maps, Google Maps, TomTom etc. /// -/// The [MapTileLayer.urlTemplate] accepts the URL in WMTS format -/// i.e. {z} — zoom level, {x} and {y} — tile coordinates. +/// The [urlTemplate] accepts the URL in WMTS format i.e. {z} — zoom level, {x} +/// and {y} — tile coordinates. /// /// This URL might vary slightly depends on the providers. The formats can be, /// https://exampleprovider/{z}/{x}/{y}.png, @@ -15,26 +160,24 @@ part of maps; /// current center point and the zoom level. /// /// The subscription key may be needed for some of the providers. Please include -/// them in the [MapTileLayer.urlTemplate] itself as mentioned in above example. -/// Please note that the format may vary between the each map providers. You can -/// check the exact URL format needed for the providers in their official -/// websites. +/// them in the [urlTemplate] itself as mentioned in above example. Please note +/// that the format may vary between the each map providers. You can check the +/// exact URL format needed for the providers in their official websites. /// /// Regarding the tile rendering, at the lowest zoom level (Level 0), the map is /// 256 x 256 pixels and the /// whole world map renders as a single tile. At each increase in level, the map /// width and height grow by a factor of 2 i.e. Level 1 is 512 x 512 pixels with /// 4 tiles ((0, 0), (0, 1), (1, 0), (1, 1) where 0 and 1 are {x} and {y} in -/// [MapTileLayer.urlTemplate]), Level 2 is 2048 x 2048 pixels with 8 +/// [urlTemplate]), Level 2 is 2048 x 2048 pixels with 8 /// tiles (from (0, 0) to (3, 3)), and so on. /// (These details are just for your information and all these calculation are /// done internally.) /// -/// However, based on the size of the [SfMaps] widget, -/// [MapTileLayer.initialFocalLatLng] and [MapTileLayer.initialZoomLevel] number -/// of initial tiles needed in the view port alone will be rendered. -/// While zooming and panning, new tiles will be requested and rendered on -/// demand based on the current zoom level and focal point. +/// However, based on the size of the [SfMaps] widget, [initialFocalLatLng] and +/// [initialZoomLevel] number of initial tiles needed in the view port alone +/// will be rendered. While zooming and panning, new tiles will be requested and +/// rendered on demand based on the current zoom level and focal point. /// The current zoom level and focal point can be obtained from the /// [MapZoomPanBehavior.zoomLevel] and [MapZoomPanBehavior.focalLatLng] /// respectively. Once the particular tile is rendered, it will be stored in the @@ -59,8 +202,8 @@ part of maps; /// ``` /// /// See also: -/// * For enabling zooming and panning, set [MapTileLayer.zoomPanBehavior] -/// with the instance of [MapZoomPanBehavior]. +/// * For enabling zooming and panning, set [zoomPanBehavior] with the instance +/// of [MapZoomPanBehavior]. class MapTileLayer extends MapLayer { /// Creates a [MapTileLayer]. MapTileLayer({ @@ -69,8 +212,11 @@ class MapTileLayer extends MapLayer { this.initialFocalLatLng = const MapLatLng(0.0, 0.0), this.initialZoomLevel = 1, this.controller, + List sublayers, int initialMarkersCount = 0, MapMarkerBuilder markerBuilder, + IndexedWidgetBuilder markerTooltipBuilder, + MapTooltipSettings tooltipSettings = const MapTooltipSettings(), MapZoomPanBehavior zoomPanBehavior, WillZoomCallback onWillZoom, WillPanCallback onWillPan, @@ -79,17 +225,20 @@ class MapTileLayer extends MapLayer { initialMarkersCount != 0 && markerBuilder != null), super( key: key, + sublayers: sublayers, initialMarkersCount: initialMarkersCount, markerBuilder: markerBuilder, + markerTooltipBuilder: markerTooltipBuilder, + tooltipSettings: tooltipSettings, zoomPanBehavior: zoomPanBehavior, onWillZoom: onWillZoom, onWillPan: onWillPan, ); - /// URL to request the tiles from the providers. + /// URL template to request the tiles from the providers. /// - /// The [MapTileLayer.urlTemplate] accepts the URL in WMTS format - /// i.e. {z} — zoom level, {x} and {y} — tile coordinates. + /// The [urlTemplate] accepts the URL in WMTS format i.e. {z} — zoom level, + /// {x} and {y} — tile coordinates. /// /// This URL might vary slightly depends on the providers. The formats can be, /// https://exampleprovider/{z}/{x}/{y}.png, @@ -100,10 +249,10 @@ class MapTileLayer extends MapLayer { /// current center point and the zoom level. /// /// The subscription key may be needed for some of the providers. Please - /// include them in the [MapTileLayer.urlTemplate] itself as mentioned in - /// above example. Please note that the format may vary between the each - /// map providers. You can check the exact URL format needed for the providers - /// in their official websites. + /// include them in the [urlTemplate] itself as mentioned in above example. + /// Please note that the format may vary between the each map provider. You + /// can check the exact URL format needed for the providers in their official + /// websites. /// /// ```dart /// @override @@ -120,11 +269,11 @@ class MapTileLayer extends MapLayer { /// } /// ``` /// - /// For Bing maps, an additional step is required. The format of the required + /// For Bing Maps, an additional step is required. The format of the required /// URL varies from the other tile services. Hence, we have added a top-level - /// [getBingUrlTemplate] method which returns the URL in the required format. - /// You can use the URL returned from this method to pass it to the - /// [MapTileLayer.urlTemplate] property. + /// [getBingUrlTemplate] function which returns the URL in the required + /// format. You can use the URL returned from this function to set it to the + /// [urlTemplate] property. /// /// ```dart /// @override @@ -153,32 +302,31 @@ class MapTileLayer extends MapLayer { /// /// Some of the providers provide different map types. For example, Bing Maps /// provide map types like Road, Aerial, AerialWithLabels etc. These types too - /// can be passed in the [MapTileLayer.urlTemplate] itself as shown in the - /// above example. You can check the official websites of the tile providers - /// to know about the available types and the code for it. + /// can be passed in the [urlTemplate] itself as shown in the above example. + /// You can check the official websites of the tile providers to know about + /// the available types and the code for it. /// /// See also: /// * For Bing Maps, use the [getBingUrlTemplate] method to get the URL in - /// required format and set it to the [MapTileLayer.urlTemplate]. + /// required format and set it to the [urlTemplate]. final String urlTemplate; /// Represents the initial focal latitude and longitude position. /// - /// Based on the size of the [SfMaps] widget,[MapTileLayer.initialFocalLatLng] - /// and [MapTileLayer.initialZoomLevel] number of initial tiles needed in the - /// view port alone will be rendered. While zooming and panning, new tiles - /// will be requested and rendered on demand based on the current zoom level - /// and focal point. The current zoom level and focal point can be obtained - /// from the [MapZoomPanBehavior.zoomLevel] and - /// [MapZoomPanBehavior.focalLatLng]. + /// Based on the size of the [SfMaps] widget, [initialFocalLatLng] and + /// [initialZoomLevel], number of initial tiles needed in the view port alone + /// will be rendered. While zooming and panning, new tiles will be requested + /// and rendered on demand based on the current zoom level and focal point. + /// The current zoom level and focal point can be obtained from the + /// [MapZoomPanBehavior.zoomLevel] and [MapZoomPanBehavior.focalLatLng]. /// - /// This properties cannot be changed dynamically. + /// This property cannot be changed dynamically. /// /// Defaults to `MapLatLng(0.0, 0.0)`. /// /// See also: - /// * For enabling zooming and panning, set [MapTileLayer.zoomPanBehavior] - /// with the instance of [MapZoomPanBehavior]. + /// * For enabling zooming and panning, set [zoomPanBehavior] with the + /// instance of [MapZoomPanBehavior]. /// * [MapZoomPanBehavior.focalLatLng], to dynamically change the center /// position. /// * [MapZoomPanBehavior.zoomLevel], to dynamically change the zoom level. @@ -186,21 +334,20 @@ class MapTileLayer extends MapLayer { /// Represents the initial zooming level. /// - /// Based on the size of the [SfMaps] widget,[MapTileLayer.initialFocalLatLng] - /// and [MapTileLayer.initialZoomLevel] number of initial tiles needed in the - /// view port alone will be rendered. While zooming and panning, new tiles - /// will be requested and rendered on demand based on the current zoom level - /// and focal point. The current zoom level and focal point can be obtained - /// from the [MapZoomPanBehavior.zoomLevel] and - /// [MapZoomPanBehavior.focalLatLng]. + /// Based on the size of the [SfMaps] widget, [initialFocalLatLng] and + /// [initialZoomLevel], number of initial tiles needed in the view port alone + /// will be rendered. While zooming and panning, new tiles will be requested + /// and rendered on demand based on the current zoom level and focal point. + /// The current zoom level and focal point can be obtained from the + /// [MapZoomPanBehavior.zoomLevel] and [MapZoomPanBehavior.focalLatLng]. /// - /// This properties cannot be changed dynamically. + /// This property cannot be changed dynamically. /// /// Defaults to 1. /// /// See also: - /// * For enabling zooming and panning, set [MapTileLayer.zoomPanBehavior] - /// with the instance of [MapZoomPanBehavior]. + /// * For enabling zooming and panning, set [zoomPanBehavior] with the + /// instance of [MapZoomPanBehavior]. /// * [MapZoomPanBehavior.focalLatLng], to dynamically change the center /// position. /// * [MapZoomPanBehavior.zoomLevel], to dynamically change the zoom level. @@ -282,14 +429,42 @@ class MapTileLayer extends MapLayer { urlTemplate: urlTemplate, initialFocalLatLng: initialFocalLatLng, initialZoomLevel: initialZoomLevel, + sublayers: sublayers, initialMarkersCount: initialMarkersCount, markerBuilder: markerBuilder, + markerTooltipBuilder: markerTooltipBuilder, + tooltipSettings: tooltipSettings, zoomPanBehavior: zoomPanBehavior, controller: controller, onWillZoom: onWillZoom, onWillPan: onWillPan, ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + + properties.add(StringProperty('urlTemplate', urlTemplate)); + properties.add(DiagnosticsProperty( + 'initialFocalLatLng', initialFocalLatLng)); + properties.add(IntProperty('initialZoomLevel', initialZoomLevel)); + if (zoomPanBehavior != null) { + properties + .add(zoomPanBehavior.toDiagnosticsNode(name: 'zoomPanBehavior')); + } + 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)); + } } class _MapTileLayer extends StatefulWidget { @@ -299,33 +474,25 @@ class _MapTileLayer extends StatefulWidget { this.initialFocalLatLng, this.initialZoomLevel, this.zoomPanBehavior, + this.sublayers, this.initialMarkersCount, this.markerBuilder, + this.markerTooltipBuilder, + this.tooltipSettings, this.controller, this.onWillZoom, this.onWillPan, }) : super(key: key); - /// Represents the actual map providers URL. final String urlTemplate; - - /// Represents the latitude and longitude position - /// which is going to position at center of the widget. - /// - /// Defaults to MapLatLng(0.0, 0.0). final MapLatLng initialFocalLatLng; - - /// Represents the initial zoom level. - /// - /// Defaults to 1. final int initialZoomLevel; - - /// Option to configure the zooming. final MapZoomPanBehavior zoomPanBehavior; - - /// Represents the number of markers needed at load time. + final List sublayers; final int initialMarkersCount; final MapMarkerBuilder markerBuilder; + final IndexedWidgetBuilder markerTooltipBuilder; + final MapTooltipSettings tooltipSettings; final MapTileLayerController controller; final WillZoomCallback onWillZoom; final WillPanCallback onWillPan; @@ -334,17 +501,25 @@ class _MapTileLayer extends StatefulWidget { _MapTileLayerState createState() => _MapTileLayerState(); } -class _MapTileLayerState extends State<_MapTileLayer> { +class _MapTileLayerState extends State<_MapTileLayer> + with TickerProviderStateMixin { // Both width and height of each tile is 256. static const double tileSize = 256; - - // The [_defaultController] handles the events of the [ZoomPanBehavior]. - _DefaultController _defaultController; - - // Stores the integer zoom level in the [_roundedZoomLevel] field + static const double _frictionCoefficient = 0.0000135; + // The [MapController] handles the events of the [ZoomPanBehavior]. + MapController _controller; + AnimationController _zoomLevelAnimationController; + AnimationController _focalLatLngAnimationController; + Animation _zoomLevelAnimation; + Animation _focalLatLngAnimation; + MapLatLngTween _focalLatLngTween; + Tween _zoomLevelTween; + + // Stores the integer zoom level in the [_nextZoomLevel] field // like 1, 2, 3 etc. - int _roundedZoomLevel; - + int _nextZoomLevel; + final Map _tiles = {}; + final Map _levels = {}; // Stores the current zoom level in the [_currentZoomLevel] field // like 1, 1.1, 1.2 etc. double _currentZoomLevel; @@ -354,11 +529,13 @@ class _MapTileLayerState extends State<_MapTileLayer> { Offset _touchStartLocalPoint; Offset _touchStartGlobalPoint; Size _size; - _Gesture _gestureType; + + GlobalKey _tooltipKey; + Gesture _gestureType; bool _isSizeChanged = false; - bool _canRequestNewTiles = false; - final Map _tiles = {}; - final Map _levels = {}; + bool _isDesktop; + bool _hasSublayer = false; + bool _isZoomedUsingToolbar = false; List _markers; MapZoomDetails _zoomDetails; MapPanDetails _panDetails; @@ -367,10 +544,14 @@ class _MapTileLayerState extends State<_MapTileLayer> { double _mouseStartZoomLevel; Offset _mouseStartLocalPoint; Offset _mouseStartGlobalPoint; + Offset _touchStartOffset; + MapLatLng _newFocalLatLng; + SfMapsThemeData _mapsThemeData; @override void initState() { super.initState(); + _tooltipKey = GlobalKey(); _currentFocalLatLng = widget.zoomPanBehavior?.focalLatLng ?? widget.initialFocalLatLng; _currentZoomLevel = (widget.zoomPanBehavior != null && @@ -378,8 +559,7 @@ class _MapTileLayerState extends State<_MapTileLayer> { ? widget.zoomPanBehavior.zoomLevel : widget.initialZoomLevel.toDouble(); widget.zoomPanBehavior?.zoomLevel = _currentZoomLevel; - _roundedZoomLevel = _currentZoomLevel.floor(); - + _nextZoomLevel = _currentZoomLevel.floor(); if (widget.controller != null) { widget.controller._markersCount = widget.initialMarkersCount; } @@ -392,42 +572,87 @@ class _MapTileLayerState extends State<_MapTileLayer> { _markers.add(marker); } - _defaultController = _DefaultController(); - widget.zoomPanBehavior?._controller = _defaultController; + _zoomLevelAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 650)) + ..addListener(_handleZoomLevelAnimation) + ..addStatusListener(_handleZoomLevelAnimationStatusChange); + _focalLatLngAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 650)) + ..addListener(_handleFocalLatLngAnimation) + ..addStatusListener(_handleFocalLatLngAnimationStatusChange); + _zoomLevelAnimation = CurvedAnimation( + parent: _zoomLevelAnimationController, curve: Curves.easeInOut); + _focalLatLngAnimation = CurvedAnimation( + parent: _focalLatLngAnimationController, curve: Curves.decelerate); + _focalLatLngTween = MapLatLngTween(); + _zoomLevelTween = Tween(); + _hasSublayer = widget.sublayers != null && widget.sublayers.isNotEmpty; widget.controller?.addListener(refreshMarkers); - _defaultController?.addZoomingListener(_handleZooming); - _defaultController?.addPanningListener(_handlePanning); - _defaultController?.addResetListener(_handleReset); - - _defaultController + _controller = MapController() + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addResetListener(_handleReset) + ..tileLayerZoomLevel = _currentZoomLevel + ..visibleFocalLatLng = _currentFocalLatLng ..onZoomLevelChange = _handleZoomTo - ..onPanChange = _handlePanTo; + ..onPanChange = _handlePanTo + ..tooltipKey = _tooltipKey + ..isTileLayerChild = true; } @override - void didUpdateWidget(_MapTileLayer oldWidget) { - if (oldWidget.zoomPanBehavior != widget.zoomPanBehavior) { - widget.zoomPanBehavior?._controller = _defaultController; + void dispose() { + if (_controller != null) { + _controller + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeResetListener(_handleReset); } - super.didUpdateWidget(oldWidget); - } + if (_zoomLevelAnimationController != null) { + _zoomLevelAnimationController + ..removeListener(_handleZoomLevelAnimation) + ..removeStatusListener(_handleZoomLevelAnimationStatusChange) + ..dispose(); + } + + if (_focalLatLngAnimationController != null) { + _focalLatLngAnimationController + ..removeListener(_handleFocalLatLngAnimation) + ..removeStatusListener(_handleFocalLatLngAnimationStatusChange) + ..dispose(); + } - @override - void dispose() { - _defaultController?.removeZoomingListener(_handleZooming); - _defaultController?.removePanningListener(_handlePanning); - _defaultController?.removeResetListener(_handleReset); widget.controller?.removeListener(refreshMarkers); - _defaultController?.dispose(); + _controller?.dispose(); _markers.clear(); _tiles?.clear(); _levels?.clear(); super.dispose(); } + @override + void didUpdateWidget(_MapTileLayer oldWidget) { + _hasSublayer = widget.sublayers != null && widget.sublayers.isNotEmpty; + super.didUpdateWidget(oldWidget); + } + @override Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + _isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows; + _mapsThemeData = SfMapsTheme.of(context); + _mapsThemeData = _mapsThemeData.copyWith( + tooltipColor: widget.tooltipSettings.color ?? _mapsThemeData.tooltipColor, + tooltipStrokeColor: widget.tooltipSettings.strokeColor ?? + _mapsThemeData.tooltipStrokeColor, + tooltipStrokeWidth: widget.tooltipSettings.strokeWidth ?? + _mapsThemeData.tooltipStrokeWidth, + tooltipBorderRadius: _mapsThemeData.tooltipBorderRadius + .resolve(Directionality.of(context)), + ); return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { if (_size != null) { @@ -435,7 +660,8 @@ class _MapTileLayerState extends State<_MapTileLayer> { _size.height != constraints.maxHeight; } - _size = _getBoxSize(constraints); + _size = getBoxSize(constraints); + _controller.tileLayerBoxSize = _size; return Container( width: _size.width, height: _size.height, @@ -448,7 +674,7 @@ class _MapTileLayerState extends State<_MapTileLayer> { onScaleStart: _handleScaleStart, onScaleUpdate: _handleScaleUpdate, onScaleEnd: _handleScaleEnd, - child: _getTileLayerElements(), + child: _getTileLayerElements(context, themeData), ), ), ); @@ -457,32 +683,59 @@ class _MapTileLayerState extends State<_MapTileLayer> { } // Add elements in the tile layer. - Widget _getTileLayerElements() { + Widget _getTileLayerElements(BuildContext context, ThemeData themeData) { final List children = []; - children.add(_getTiles()); + if (_hasSublayer) { + children.add( + ClipRect( + child: SublayerContainer( + controller: _controller, + tooltipKey: _tooltipKey, + children: widget.sublayers, + ), + ), + ); + } + if (_markers != null && _markers.isNotEmpty) { - children.add(_MapTileMarkerRenderObject( + children.add(ClipRect( + child: _TileLayerMarkerContainer( + tooltipKey: _tooltipKey, + markerTooltipBuilder: widget.markerTooltipBuilder, children: _markers, - state: this, - )); + controller: _controller, + ))); } if (widget.zoomPanBehavior != null) { - children.add(_BehaviorViewRenderObjectWidget( - defaultController: _defaultController, + children.add(BehaviorViewRenderObjectWidget( + controller: _controller, zoomPanBehavior: widget.zoomPanBehavior, )); } if (widget.zoomPanBehavior != null && widget.zoomPanBehavior.showToolbar && - kIsWeb) { + _isDesktop) { children.add( - _MapToolbar( + MapToolbar( onWillZoom: widget.onWillZoom, zoomPanBehavior: widget.zoomPanBehavior, - defaultController: _defaultController, + controller: _controller, + ), + ); + } + + if (_hasTooltipBuilder()) { + children.add( + MapTooltip( + key: _tooltipKey, + controller: _controller, + sublayers: widget.sublayers, + markerTooltipBuilder: widget.markerTooltipBuilder, + tooltipSettings: widget.tooltipSettings, + themeData: _mapsThemeData, ), ); } @@ -492,6 +745,25 @@ class _MapTileLayerState extends State<_MapTileLayer> { ); } + bool _hasTooltipBuilder() { + if (widget.markerTooltipBuilder != null) { + return true; + } else if (_hasSublayer) { + final Iterator iterator = widget.sublayers.iterator; + while (iterator.moveNext()) { + final MapSublayer sublayer = iterator.current; + if ((sublayer is MapShapeSublayer && + (sublayer.shapeTooltipBuilder != null || + sublayer.bubbleTooltipBuilder != null || + sublayer.markerTooltipBuilder != null)) || + (sublayer is MapVectorLayer && sublayer.tooltipBuilder != null)) { + return true; + } + } + } + return false; + } + // Generate tiles for the visible bounds and placed the tiles in a positioned // widget. Widget _getTiles() { @@ -515,16 +787,10 @@ class _MapTileLayerState extends State<_MapTileLayer> { // Generate tiles for the new zoom level based on the focalLatLng value. void _requestAndPopulateNewTiles() { - if (_gestureType != null && - _gestureType != _Gesture.pan && - !_canRequestNewTiles) { - return; - } - _updateZoomLevel(); - final double tileCount = pow(2, _roundedZoomLevel).toDouble(); + final double tileCount = pow(2, _nextZoomLevel).toDouble(); final Offset actualCenterPixelPosition = - _getPixelFromLatLng(_currentFocalLatLng, _roundedZoomLevel.toDouble()); + _pixelFromLatLng(_currentFocalLatLng, _currentZoomLevel); final Offset halfSize = Offset(_size.width, _size.height) / 2; @@ -537,19 +803,21 @@ class _MapTileLayerState extends State<_MapTileLayer> { // bounds of the tiles in pixel based on the [focalLatLng] value. final Offset visualTileStart = actualCenterPixelPosition - halfSize; final Offset visualTileEnd = actualCenterPixelPosition + halfSize; + final double currentLevelTileSize = + tileSize * _levels[_nextZoomLevel].scale; // The [startX], [startY], [endX] and [endY] represents the visual // bounds of the tiles in factor. - final int startX = (visualTileStart.dx / tileSize).floor(); - final int startY = (visualTileStart.dy / tileSize).floor(); - final int endX = (visualTileEnd.dx / tileSize).ceil(); - final int endY = (visualTileEnd.dy / tileSize).ceil(); + final int startX = (visualTileStart.dx / currentLevelTileSize).floor(); + final int startY = (visualTileStart.dy / currentLevelTileSize).floor(); + final int endX = (visualTileEnd.dx / currentLevelTileSize).ceil(); + final int endY = (visualTileEnd.dy / currentLevelTileSize).ceil(); final List<_MapTileCoordinate> tileCoordinates = <_MapTileCoordinate>[]; for (int i = startX; i <= endX; i++) { for (int j = startY; j <= endY; j++) { final _MapTileCoordinate tileCoordinate = - _MapTileCoordinate(i, j, _roundedZoomLevel); + _MapTileCoordinate(i, j, _nextZoomLevel); if ((tileCoordinate.x < globalTileStart.dx || tileCoordinate.x > globalTileEnd.dx) || @@ -557,7 +825,13 @@ class _MapTileLayerState extends State<_MapTileLayer> { tileCoordinate.y > globalTileEnd.dy)) { continue; } - tileCoordinates.add(tileCoordinate); + + final _MapTile tile = _tiles[_tileFactorToKey(tileCoordinate)]; + if (tile != null) { + continue; + } else { + tileCoordinates.add(tileCoordinate); + } } } @@ -577,6 +851,7 @@ class _MapTileLayerState extends State<_MapTileLayer> { if (widget.zoomPanBehavior != null) { if (widget.zoomPanBehavior.enablePinching || widget.zoomPanBehavior.enablePanning) { + _focalLatLngAnimationController?.stop(); _gestureType = null; _touchStartLocalPoint = details.localFocalPoint; _touchStartGlobalPoint = details.focalPoint; @@ -585,29 +860,28 @@ class _MapTileLayerState extends State<_MapTileLayer> { (_size.width / 2) - _touchStartLocalPoint.dx, (_size.height / 2) - _touchStartLocalPoint.dy); final Offset actualCenterPixelPosition = - _getPixelFromLatLng(_currentFocalLatLng, _touchStartZoomLevel); + _pixelFromLatLng(_currentFocalLatLng, _touchStartZoomLevel); final Offset point = actualCenterPixelPosition - localPointCenterDiff; _touchStartLatLng = - _getLatLngFromPixel(point, scale: _touchStartZoomLevel); + _latLngFromPixel(point, scale: _touchStartZoomLevel); final Rect newVisibleBounds = Rect.fromCenter( - center: - _getPixelFromLatLng(_currentFocalLatLng, _touchStartZoomLevel), + center: _pixelFromLatLng(_currentFocalLatLng, _touchStartZoomLevel), width: _size.width, height: _size.height); final MapLatLngBounds newVisibleLatLng = MapLatLngBounds( - _getLatLngFromPixel(newVisibleBounds.topRight, + _latLngFromPixel(newVisibleBounds.topRight, scale: _touchStartZoomLevel), - _getLatLngFromPixel(newVisibleBounds.bottomLeft, + _latLngFromPixel(newVisibleBounds.bottomLeft, scale: _touchStartZoomLevel)); - - _zoomDetails = MapZoomDetails( - newVisibleBounds: newVisibleLatLng, - ).._newFocalLatLng = _currentFocalLatLng; - - _panDetails = MapPanDetails( - newVisibleBounds: newVisibleLatLng, - ).._newFocalLatLng = _currentFocalLatLng; + final MapLatLng touchStartFocalLatLng = _calculateVisibleLatLng( + _touchStartLocalPoint, _touchStartZoomLevel); + _touchStartOffset = + _pixelFromLatLng(touchStartFocalLatLng, _touchStartZoomLevel); + + _zoomDetails = MapZoomDetails(newVisibleBounds: newVisibleLatLng); + _panDetails = MapPanDetails(newVisibleBounds: newVisibleLatLng); + _newFocalLatLng = _currentFocalLatLng; } } } @@ -630,19 +904,35 @@ class _MapTileLayerState extends State<_MapTileLayer> { return; } + _controller.localScale = details.scale; final double newZoomLevel = _getZoomLevel(_touchStartZoomLevel + log(details.scale) / ln2); - final MapLatLng newFocalLatLng = - _calculateVisibleLatLng(details.localFocalPoint, newZoomLevel); switch (_gestureType) { - case _Gesture.scale: + case Gesture.scale: if (widget.zoomPanBehavior.enablePinching) { + final MapLatLng newFocalLatLng = + _calculateVisibleLatLng(_touchStartLocalPoint, newZoomLevel); + _controller + ..isInInteractive = true + ..gesture = _gestureType + ..localScale = details.scale + ..pinchCenter = _touchStartLocalPoint; _invokeOnZooming(newZoomLevel, _touchStartLocalPoint, _touchStartGlobalPoint, newFocalLatLng); } return; - case _Gesture.pan: + case Gesture.pan: if (widget.zoomPanBehavior.enablePanning) { + final MapLatLng newFocalLatLng = + _calculateVisibleLatLng(details.localFocalPoint, newZoomLevel); + final Offset newFocalOffset = + _pixelFromLatLng(newFocalLatLng, newZoomLevel); + + _controller + ..isInInteractive = true + ..gesture = _gestureType + ..localScale = 1.0 + ..panDistance = _touchStartOffset - newFocalOffset; _invokeOnPanning(newZoomLevel, localFocalPoint: details.localFocalPoint, globalFocalPoint: details.focalPoint, @@ -655,9 +945,56 @@ class _MapTileLayerState extends State<_MapTileLayer> { } void _handleScaleEnd(ScaleEndDetails details) { + // Calculating the end focalLatLng based on the obtained velocity. + if (_gestureType == Gesture.pan && + details.velocity.pixelsPerSecond.distance >= kMinFlingVelocity) { + final Offset currentPixelPoint = + _pixelFromLatLng(_currentFocalLatLng, _currentZoomLevel); + final FrictionSimulation frictionX = FrictionSimulation( + _frictionCoefficient, + currentPixelPoint.dx, + -details.velocity.pixelsPerSecond.dx, + ); + + final FrictionSimulation frictionY = FrictionSimulation( + _frictionCoefficient, + currentPixelPoint.dy, + -details.velocity.pixelsPerSecond.dy, + ); + + final double duration = + _getDuration(details.velocity.pixelsPerSecond.distance); + + final MapLatLng latLng = _latLngFromPixel( + Offset(frictionX.finalX, frictionY.finalX), + scale: _currentZoomLevel); + _gestureType = null; + _focalLatLngAnimationController.duration = + Duration(milliseconds: (duration * 650).round()); + widget.zoomPanBehavior.focalLatLng = latLng; + } + + _controller.localScale = 1.0; _gestureType = null; _panDetails = null; _zoomDetails = null; + _invalidateSublayer(); + } + + // Calculate the time at which movement comes to a stop. + double _getDuration(double distance) { + return log(10.0 / distance) / log(_frictionCoefficient / 100); + } + + void _invalidateSublayer() { + _controller + ..isInInteractive = false + ..normalize = Offset.zero + ..gesture = null + ..localScale = 1.0; + if (_hasSublayer) { + _controller?.notifyRefreshListeners(); + } } // This method called for both pinch zooming action and mouse wheel zooming @@ -668,7 +1005,7 @@ class _MapTileLayerState extends State<_MapTileLayer> { Offset globalFocalPoint, MapLatLng newFocalLatLng]) { final Rect newVisibleBounds = Rect.fromCenter( - center: _getPixelFromLatLng(newFocalLatLng, newZoomLevel), + center: _pixelFromLatLng(newFocalLatLng, newZoomLevel), width: _size.width, height: _size.height); @@ -679,13 +1016,12 @@ class _MapTileLayerState extends State<_MapTileLayer> { newZoomLevel: newZoomLevel, previousVisibleBounds: _zoomDetails != null ? _zoomDetails.newVisibleBounds - : _defaultController.visibleLatLngBounds, + : _controller.visibleLatLngBounds, newVisibleBounds: MapLatLngBounds( - _getLatLngFromPixel(newVisibleBounds.topRight, scale: newZoomLevel), - _getLatLngFromPixel(newVisibleBounds.bottomLeft, - scale: newZoomLevel))) - .._newFocalLatLng = newFocalLatLng; - + _latLngFromPixel(newVisibleBounds.topRight, scale: newZoomLevel), + _latLngFromPixel(newVisibleBounds.bottomLeft, + scale: newZoomLevel))); + _newFocalLatLng = newFocalLatLng; if (widget.onWillZoom == null || widget.onWillZoom(_zoomDetails)) { widget.zoomPanBehavior?.onZooming(_zoomDetails); } @@ -697,7 +1033,7 @@ class _MapTileLayerState extends State<_MapTileLayer> { Offset touchStartLocalPoint, MapLatLng newFocalLatLng}) { final Rect newVisibleBounds = Rect.fromCenter( - center: _getPixelFromLatLng(newFocalLatLng, newZoomLevel), + center: _pixelFromLatLng(newFocalLatLng, newZoomLevel), width: _size.width, height: _size.height); @@ -708,13 +1044,12 @@ class _MapTileLayerState extends State<_MapTileLayer> { delta: localFocalPoint - touchStartLocalPoint, previousVisibleBounds: _panDetails != null ? _panDetails.newVisibleBounds - : _defaultController.visibleLatLngBounds, + : _controller.visibleLatLngBounds, newVisibleBounds: MapLatLngBounds( - _getLatLngFromPixel(newVisibleBounds.topRight, scale: newZoomLevel), - _getLatLngFromPixel(newVisibleBounds.bottomLeft, - scale: newZoomLevel))) - .._newFocalLatLng = newFocalLatLng; - + _latLngFromPixel(newVisibleBounds.topRight, scale: newZoomLevel), + _latLngFromPixel(newVisibleBounds.bottomLeft, + scale: newZoomLevel))); + _newFocalLatLng = newFocalLatLng; if (widget.onWillPan == null || widget.onWillPan(_panDetails)) { widget.zoomPanBehavior?.onPanning(_panDetails); } @@ -724,11 +1059,11 @@ class _MapTileLayerState extends State<_MapTileLayer> { // [widget.zoomPanBehavior.minZoomLevel] or // [widget.zoomPanBehavior.maxZoomLevel] if the new zoom level value is not // in zoom level range. - double _getZoomLevel(double scale) { - return scale.isNaN + double _getZoomLevel(double zoomLevel) { + return zoomLevel.isNaN ? widget.zoomPanBehavior.minZoomLevel - : _interpolateValue( - scale, + : interpolateValue( + zoomLevel, widget.zoomPanBehavior.minZoomLevel, widget.zoomPanBehavior.maxZoomLevel, ); @@ -738,20 +1073,72 @@ class _MapTileLayerState extends State<_MapTileLayer> { // ZoomPanBehavior. void _handleZoomTo(double zoomLevel, {MapLatLng latlng}) { if (_gestureType == null) { - _invokeOnZooming(widget.zoomPanBehavior.zoomLevel, Offset.zero, - Offset.zero, latlng ?? _currentFocalLatLng); + _zoomLevelTween.begin = _currentZoomLevel; + _zoomLevelTween.end = widget.zoomPanBehavior.zoomLevel; + _zoomLevelAnimationController.forward(from: 0.0); } } // This method called when dynamically changing the [focalLatLng] property of // ZoomPanBehavior. void _handlePanTo(MapLatLng latlng) { - if (_gestureType == null && widget.zoomPanBehavior.focalLatLng != null) { - _invokeOnPanning(widget.zoomPanBehavior.zoomLevel, - localFocalPoint: Offset.zero, - globalFocalPoint: Offset.zero, - touchStartLocalPoint: Offset.zero, - newFocalLatLng: widget.zoomPanBehavior.focalLatLng); + if (_gestureType == null) { + _focalLatLngTween.begin = _currentFocalLatLng; + _focalLatLngTween.end = widget.zoomPanBehavior.focalLatLng; + _focalLatLngAnimationController.forward(from: 0.0); + } + } + + void _handleZoomLevelAnimation() { + if (_zoomLevelTween.end != null) { + _currentZoomLevel = _zoomLevelTween.evaluate(_zoomLevelAnimation); + } + _handleZoomPanAnimation(); + } + + void _handleFocalLatLngAnimation() { + if (_focalLatLngTween.end != null) { + _currentFocalLatLng = _focalLatLngTween.evaluate(_focalLatLngAnimation); + } + _handleZoomPanAnimation(); + } + + void _handleZoomPanAnimation() { + setState(() { + _handleTransform(); + if (_hasSublayer) { + _controller.visibleFocalLatLng = _currentFocalLatLng; + _controller.tileLayerZoomLevel = _currentZoomLevel; + _invalidateSublayer(); + } else { + _controller.notifyRefreshListeners(); + } + }); + } + + void _handleZoomLevelAnimationStatusChange(AnimationStatus status) { + if (status == AnimationStatus.completed) { + if (_zoomLevelTween.end != null && !_isZoomedUsingToolbar) { + _invokeOnZooming(widget.zoomPanBehavior.zoomLevel, Offset.zero, + Offset.zero, _currentFocalLatLng); + } else { + _isZoomedUsingToolbar = false; + } + } + } + + void _handleFocalLatLngAnimationStatusChange(AnimationStatus status) { + if (status == AnimationStatus.completed && _focalLatLngTween.end != null) { + final Offset previousFocalPoint = + _pixelFromLatLng(_focalLatLngTween.begin, _currentZoomLevel); + final Offset currentFocalPoint = + _pixelFromLatLng(_focalLatLngTween.end, _currentZoomLevel); + _invokeOnPanning(_currentZoomLevel, + localFocalPoint: currentFocalPoint, + touchStartLocalPoint: previousFocalPoint, + newFocalLatLng: _currentFocalLatLng); + _focalLatLngAnimationController.duration = + const Duration(milliseconds: 650); } } @@ -760,8 +1147,8 @@ class _MapTileLayerState extends State<_MapTileLayer> { if (widget.zoomPanBehavior != null && widget.zoomPanBehavior.enablePinching) { if (event is PointerScrollEvent) { - widget.zoomPanBehavior.handleEvent(event, null); - _gestureType = _Gesture.scale; + widget.zoomPanBehavior.handleEvent(event); + _gestureType = Gesture.scale; _mouseStartZoomLevel ??= _currentZoomLevel; _mouseCenterLatLng ??= _currentFocalLatLng; _mouseStartLocalPoint ??= event.localPosition; @@ -771,16 +1158,33 @@ class _MapTileLayerState extends State<_MapTileLayer> { (_size.width / 2) - _mouseStartLocalPoint.dx, (_size.height / 2) - _mouseStartLocalPoint.dy); final Offset actualCenterPixelPosition = - _getPixelFromLatLng(_mouseCenterLatLng, _mouseStartZoomLevel); + _pixelFromLatLng(_mouseCenterLatLng, _mouseStartZoomLevel); final Offset point = actualCenterPixelPosition - localPointCenterDiff; _touchStartLatLng = - _getLatLngFromPixel(point, scale: _mouseStartZoomLevel); - - final double scale = _defaultController.localScale - + _latLngFromPixel(point, scale: _mouseStartZoomLevel); + double scale = _controller.tileLayerLocalScale - (event.scrollDelta.dy / _size.height); + _controller.tileLayerLocalScale = scale; final double newZoomLevel = _getZoomLevel(_mouseStartZoomLevel + log(scale) / ln2); - _defaultController.localScale = scale; + // If the scale * _mouseStartZoomLevel value goes beyond + // minZoomLevel, setted the min scale value. + if (scale * _mouseStartZoomLevel < + widget.zoomPanBehavior.minZoomLevel) { + scale = widget.zoomPanBehavior.minZoomLevel / _mouseStartZoomLevel; + } + // If the scale * _mouseStartZoomLevel value goes beyond + // maxZoomLevel, setted the max scale value. + else if (scale * _mouseStartZoomLevel > + widget.zoomPanBehavior.maxZoomLevel) { + scale = widget.zoomPanBehavior.maxZoomLevel / _mouseStartZoomLevel; + } + + _controller + ..isInInteractive = true + ..gesture = _gestureType + ..localScale = scale + ..pinchCenter = event.localPosition; final MapLatLng newFocalLatLng = _calculateVisibleLatLng(event.localPosition, newZoomLevel); _invokeOnZooming(newZoomLevel, _mouseStartLocalPoint, @@ -788,7 +1192,8 @@ class _MapTileLayerState extends State<_MapTileLayer> { _zoomingDelayTimer?.cancel(); _zoomingDelayTimer = Timer(const Duration(milliseconds: 300), () { - _defaultController.localScale = 1.0; + _invalidateSublayer(); + _controller.tileLayerLocalScale = 1.0; _mouseStartZoomLevel = null; _mouseStartLocalPoint = null; _mouseStartGlobalPoint = null; @@ -800,19 +1205,19 @@ class _MapTileLayerState extends State<_MapTileLayer> { } void _handlePointerDown(PointerDownEvent event) { - widget.zoomPanBehavior?.handleEvent(event, null); + widget.zoomPanBehavior?.handleEvent(event); } void _handlePointerMove(PointerMoveEvent event) { - widget.zoomPanBehavior?.handleEvent(event, null); + widget.zoomPanBehavior?.handleEvent(event); } void _handlePointerUp(PointerUpEvent event) { - widget.zoomPanBehavior?.handleEvent(event, null); + widget.zoomPanBehavior?.handleEvent(event); } // Check whether gesture type is scale or pan. - _Gesture _getGestureType(double scale, Offset focalPoint) { + Gesture _getGestureType(double scale, Offset focalPoint) { // The minimum distance required to start scale or pan gesture. final int minScaleDistance = 3; if (_touchStartLocalPoint != null) { @@ -821,13 +1226,13 @@ class _MapTileLayerState extends State<_MapTileLayer> { final Offset distance = focalPoint - _touchStartLocalPoint; return distance.dx.abs() > minScaleDistance || distance.dy.abs() > minScaleDistance - ? _Gesture.pan + ? Gesture.pan : null; } return (distance.dx.abs() > minScaleDistance || distance.dy.abs() > minScaleDistance) - ? _Gesture.scale + ? Gesture.scale : null; } return null; @@ -836,30 +1241,31 @@ class _MapTileLayerState extends State<_MapTileLayer> { // This method invoked when user override the [onZooming] method in // [ZoomPanBehavior] and called [super.onZooming(details)]. void _handleZooming(MapZoomDetails details) { - _currentZoomLevel = details.newZoomLevel; if (_gestureType != null) { // Updating map while pinching and scrolling. - _currentFocalLatLng = details._newFocalLatLng; - _defaultController.visibleFocalLatLng = _currentFocalLatLng; - _defaultController.visibleLatLngBounds = details.newVisibleBounds; + _currentZoomLevel = details.newZoomLevel; + _controller.tileLayerZoomLevel = _currentZoomLevel; + _currentFocalLatLng = _newFocalLatLng; + _controller.visibleFocalLatLng = _currentFocalLatLng; + _controller.visibleLatLngBounds = details.newVisibleBounds; widget.zoomPanBehavior.focalLatLng = _currentFocalLatLng; + setState(() { + _handleTransform(); + }); } else { // Updating map via toolbar. final Rect newVisibleBounds = Rect.fromCenter( - center: _getPixelFromLatLng(_currentFocalLatLng, _currentZoomLevel), + center: _pixelFromLatLng(_currentFocalLatLng, details.newZoomLevel), width: _size.width, height: _size.height); - _defaultController.visibleLatLngBounds = MapLatLngBounds( - _getLatLngFromPixel(newVisibleBounds.topRight, - scale: _currentZoomLevel), - _getLatLngFromPixel(newVisibleBounds.bottomLeft, - scale: _currentZoomLevel)); + _controller.visibleLatLngBounds = MapLatLngBounds( + _latLngFromPixel(newVisibleBounds.topRight, + scale: details.newZoomLevel), + _latLngFromPixel(newVisibleBounds.bottomLeft, + scale: details.newZoomLevel)); + _isZoomedUsingToolbar = true; } - setState(() { - _handleTransform(); - }); - widget.zoomPanBehavior.zoomLevel = details.newZoomLevel; } @@ -867,10 +1273,10 @@ class _MapTileLayerState extends State<_MapTileLayer> { // [ZoomPanBehavior] and called [super.onPanning(details)]. void _handlePanning(MapPanDetails details) { setState(() { - _currentFocalLatLng = details._newFocalLatLng; + _currentFocalLatLng = _newFocalLatLng; widget.zoomPanBehavior.focalLatLng = _currentFocalLatLng; - _defaultController.visibleFocalLatLng = _currentFocalLatLng; - _defaultController.visibleLatLngBounds = details.newVisibleBounds; + _controller.visibleFocalLatLng = _currentFocalLatLng; + _controller.visibleLatLngBounds = details.newVisibleBounds; _handleTransform(); }); } @@ -878,11 +1284,7 @@ class _MapTileLayerState extends State<_MapTileLayer> { // This method invoked when user called the [reset] method in // [ZoomPanBehavior]. void _handleReset() { - setState(() { - _currentZoomLevel = widget.zoomPanBehavior.minZoomLevel; - widget.zoomPanBehavior.zoomLevel = widget.zoomPanBehavior.minZoomLevel; - _handleTransform(); - }); + widget.zoomPanBehavior.zoomLevel = widget.zoomPanBehavior.minZoomLevel; } // Calculate new focal coordinate value while scaling, panning or mouse wheel @@ -890,18 +1292,18 @@ class _MapTileLayerState extends State<_MapTileLayer> { MapLatLng _calculateVisibleLatLng( Offset localFocalPoint, double newZoomLevel) { final Offset focalStartPoint = - _getPixelFromLatLng(_touchStartLatLng, newZoomLevel); + _pixelFromLatLng(_touchStartLatLng, newZoomLevel); final Offset newCenterPoint = focalStartPoint - localFocalPoint + Offset(_size.width / 2, _size.height / 2); - return _getLatLngFromPixel(newCenterPoint, scale: newZoomLevel); + return _latLngFromPixel(newCenterPoint, scale: newZoomLevel); } // This method used to move each tile based on the amount of // translation and scaling value in the respective levels. Widget _getPositionedTiles(_MapTile tile) { final Offset tilePos = tile.tilePos; - final _MapZoomLevel level = tile.level; + final MapZoomLevel level = tile.level; final double tileLeftPos = (tilePos.dx * level.scale) + level.translatePoint.dx; final double tileTopPos = @@ -921,7 +1323,7 @@ class _MapTileLayerState extends State<_MapTileLayer> { void _addTile(_MapTileCoordinate tileFactor) { final String tileFactorToKey = _tileFactorToKey(tileFactor); final String url = _getTileUrl(tileFactor.x, tileFactor.y, tileFactor.z); - final _MapZoomLevel level = _levels[tileFactor.z]; + final MapZoomLevel level = _levels[tileFactor.z]; final double tileLeftPos = (tileFactor.x * tileSize) - level.origin.dx; final double tileTopPos = (tileFactor.y * tileSize) - level.origin.dy; @@ -937,7 +1339,7 @@ class _MapTileLayerState extends State<_MapTileLayer> { ); } - // Converts the [MapTileLayer.urlTemplate] format into respective map + // Converts the [urlTemplate] format into respective map // providers URL format. String _getTileUrl(int x, int y, int z) { String previousLetter; @@ -992,15 +1394,13 @@ class _MapTileLayerState extends State<_MapTileLayer> { // new tiles if the current zoom level value reached next zoom level. void _handleTransform() { final double tileZoom = _currentZoomLevel; - if ((tileZoom - _roundedZoomLevel).abs() >= 1) { + if ((tileZoom - _nextZoomLevel).abs() >= 1) { // Request new tiles when next zoom level reached. - _canRequestNewTiles = true; - _roundedZoomLevel = tileZoom.toInt(); + _nextZoomLevel = tileZoom.toInt(); _requestAndPopulateNewTiles(); _updateZoomLevelTransforms(_currentFocalLatLng, _currentZoomLevel); } else { // Scale and transform the existing tiles. - _canRequestNewTiles = false; _updateZoomLevelTransforms(_currentFocalLatLng, _currentZoomLevel); } } @@ -1016,23 +1416,27 @@ class _MapTileLayerState extends State<_MapTileLayer> { // Calculate amount of scale and translation for each zoom level while // scaling. void _updateZoomLevelTransform( - _MapZoomLevel level, MapLatLng center, double zoom) { - final double scaleDiff = - _getTotalTileWidth(zoom) / _getTotalTileWidth(level.zoom); - final Offset scaledPixelOrigin = _getLevelOrigin(center, zoom); + MapZoomLevel level, MapLatLng center, double zoom) { if (level.origin == null) { return; } - + final double currentScale = + getTotalTileWidth(zoom) / getTotalTileWidth(level.zoom); + final Offset scaledPixelOrigin = _getLevelOrigin(center, zoom); level.translatePoint = Offset( - (level.origin.dx * scaleDiff) - scaledPixelOrigin.dx, - (level.origin.dy * scaleDiff) - scaledPixelOrigin.dy); - level.scale = scaleDiff; + (level.origin.dx * currentScale) - scaledPixelOrigin.dx, + (level.origin.dy * currentScale) - scaledPixelOrigin.dy); + level.scale = currentScale; + + if (level.zoom == _nextZoomLevel) { + _controller.tileCurrentLevelDetails.translatePoint = level.translatePoint; + _controller.tileCurrentLevelDetails.scale = level.scale; + } } // Calculate tiles visual bounds origin for each new zoom level. - _MapZoomLevel _updateZoomLevel() { - final int zoom = _roundedZoomLevel; + MapZoomLevel _updateZoomLevel() { + final int zoom = _nextZoomLevel; if (zoom == null) { return null; } @@ -1055,17 +1459,17 @@ class _MapTileLayerState extends State<_MapTileLayer> { _levels.remove(levelKey); } - _MapZoomLevel level = _levels[zoom]; + MapZoomLevel level = _levels[zoom]; if (level == null) { // The [_levels] collection contains each integer zoom level origin, // scale and translationPoint. The scale and translationPoint are // calculated in every pinch zoom level for scaling the tiles. - level = _levels[zoom.toDouble()] = _MapZoomLevel(); + level = _levels[zoom.toDouble()] = MapZoomLevel(); level.origin = _getLevelOrigin(_currentFocalLatLng, zoom.toDouble()) ?? Offset.zero; level.zoom = zoom.toDouble(); - if (_gestureType != null && _gestureType == _Gesture.scale) { + if (_gestureType != null && _gestureType == Gesture.scale) { _updateZoomLevelTransform( level, _currentFocalLatLng, _currentZoomLevel); } else { @@ -1077,20 +1481,19 @@ class _MapTileLayerState extends State<_MapTileLayer> { // Recalculate tiles bounds origin for all existing zoom level // when size changed. if (_isSizeChanged) { - for (final level in _levels.entries) { - _levels[level.key].origin = _getLevelOrigin( - _currentFocalLatLng, _levels[level.key].zoom.toDouble()) ?? - Offset.zero; - _updateZoomLevelTransforms(_currentFocalLatLng, _currentZoomLevel); - } + _updateZoomLevelTransforms(_currentFocalLatLng, _currentZoomLevel); } + final double totalTileWidth = getTotalTileWidth(level.zoom); + _controller + ..totalTileSize = Size(totalTileWidth, totalTileWidth) + ..tileCurrentLevelDetails = _levels[_nextZoomLevel]; return level; } Offset _getLevelOrigin(MapLatLng center, double zoom) { final Offset halfSize = Offset(_size.width / 2.0, _size.height / 2.0); - return _getPixelFromLatLng(center, zoom) - halfSize; + return _pixelFromLatLng(center, zoom) - halfSize; } void _removeTilesAtZoom(double zoom) { @@ -1120,42 +1523,42 @@ class _MapTileLayerState extends State<_MapTileLayer> { return '${tileFactor.x}:${tileFactor.y}:${tileFactor.z}'; } - void refreshMarkers() { + void refreshMarkers([MarkerAction action, List indices]) { MapMarker marker; - switch (widget.controller._markerAction) { - case _MarkerAction.insert: - marker = widget.markerBuilder(context, widget.controller._index); - if (widget.controller._index < widget.controller._markersCount) { - _markers.insert(widget.controller._index, marker); - } else if (widget.controller._index == - widget.controller._markersCount) { + switch (action) { + case MarkerAction.insert: + int index = indices[0]; + assert(index <= widget.controller._markersCount); + if (index > widget.controller._markersCount) { + index = widget.controller._markersCount; + } + marker = widget.markerBuilder(context, index); + if (index < widget.controller._markersCount) { + _markers.insert(index, marker); + } else if (index == widget.controller._markersCount) { _markers.add(marker); } widget.controller._markersCount++; break; - case _MarkerAction.removeAt: - assert(widget.controller._index < widget.controller._markersCount); - _markers.removeAt(widget.controller._index); + case MarkerAction.removeAt: + final int index = indices[0]; + assert(index < widget.controller._markersCount); + _markers.removeAt(index); widget.controller._markersCount--; break; - case _MarkerAction.replace: - for (final int index in widget.controller._replaceableIndices) { + case MarkerAction.replace: + for (final int index in indices) { assert(index < widget.controller._markersCount); marker = widget.markerBuilder(context, index); - assert(marker != null); _markers[index] = marker; } break; - case _MarkerAction.clear: + case MarkerAction.clear: _markers.clear(); widget.controller._markersCount = 0; break; - case _MarkerAction.none: - break; } - widget.controller._index = -1; - setState(() { // Rebuilds to visually update the markers when it was updated or added. }); @@ -1198,7 +1601,7 @@ class _MapTileCoordinate { } /// Provides the information about the current and previous zoom level. -class _MapZoomLevel { +class MapZoomLevel { /// Represents the visual tiles origin. Offset origin; @@ -1234,7 +1637,7 @@ class _MapTile { final Offset tilePos; /// Represents the scale transform details of the tile. - final _MapZoomLevel level; + final MapZoomLevel level; /// Add tile to the image. final Widget image; @@ -1248,29 +1651,60 @@ class _MapTile { } } -class _MapTileMarkerRenderObject extends Stack { - _MapTileMarkerRenderObject({ - Key key, +class _TileLayerMarkerContainer extends Stack { + _TileLayerMarkerContainer({ + Key tooltipKey, + IndexedWidgetBuilder markerTooltipBuilder, List children, - _MapTileLayerState state, - }) : state = state, + MapController controller, + }) : tooltipKey = tooltipKey, + markerTooltipBuilder = markerTooltipBuilder, + controller = controller, super( - key: key, children: children ?? [], ); - final _MapTileLayerState state; + final Key tooltipKey; + final IndexedWidgetBuilder markerTooltipBuilder; + final MapController controller; @override _MapRenderTileMarker createRenderObject(BuildContext context) { - return _MapRenderTileMarker(state: state); + return _MapRenderTileMarker( + tooltipKey: tooltipKey, + markerTooltipBuilder: markerTooltipBuilder, + controller: controller, + markerContainer: this, + ); + } + + @override + void updateRenderObject( + BuildContext context, _MapRenderTileMarker renderObject) { + renderObject + ..markerTooltipBuilder = markerTooltipBuilder + ..markerContainer = this; } } class _MapRenderTileMarker extends RenderStack { - _MapRenderTileMarker({this.state}); + _MapRenderTileMarker({ + this.tooltipKey, + this.markerTooltipBuilder, + this.state, + this.markerContainer, + this.controller, + }); + Key tooltipKey; + IndexedWidgetBuilder markerTooltipBuilder; + MapController controller; _MapTileLayerState state; + _TileLayerMarkerContainer markerContainer; + + int getMarkerIndex(MapMarker marker) { + return markerContainer.children.indexOf(marker); + } void _handleZooming(MapZoomDetails details) { markNeedsLayout(); @@ -1284,43 +1718,47 @@ class _MapRenderTileMarker extends RenderStack { markNeedsLayout(); } + void _handleRefresh() { + markNeedsLayout(); + } + @override void attach(PipelineOwner owner) { super.attach(owner); - state._defaultController.addZoomingListener(_handleZooming); - state._defaultController.addPanningListener(_handlePanning); - state._defaultController.addResetListener(_handleReset); + controller + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addResetListener(_handleReset) + ..addRefreshListener(_handleRefresh); } @override void detach() { - state._defaultController.removeZoomingListener(_handleZooming); - state._defaultController.removePanningListener(_handlePanning); - state._defaultController.removeResetListener(_handleReset); + controller + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeResetListener(_handleReset) + ..removeRefreshListener(_handleRefresh); super.detach(); } @override void performLayout() { - size = state._size; + size = controller.tileLayerBoxSize; RenderBox child = firstChild; while (child != null) { - final _RenderMapMarker marker = child; + final RenderMapMarker marker = child; final StackParentData childParentData = child.parentData; child.layout(constraints, parentUsesSize: true); - final Offset pixelValues = _getPixelFromLatLng( + final Offset pixelValues = _pixelFromLatLng( MapLatLng(marker.latitude, marker.longitude), - state._roundedZoomLevel.toDouble()); - final _MapZoomLevel level = - state._levels[state._roundedZoomLevel.toDouble()]; + controller.tileCurrentLevelDetails.zoom); + final MapZoomLevel level = controller.tileCurrentLevelDetails; final Offset translationOffset = pixelValues - level.origin; childParentData.offset = Offset(translationOffset.dx * level.scale, translationOffset.dy * level.scale) + - level.translatePoint; - if (marker.child != null) { - childParentData.offset -= - Offset(child.size.width / 2, child.size.height / 2); - } + level.translatePoint - + Offset(child.size.width / 2, child.size.height / 2); child = childParentData.nextSibling; } } @@ -1328,6 +1766,8 @@ class _MapRenderTileMarker extends RenderStack { @override void paint(PaintingContext context, Offset offset) { RenderBox child = firstChild; + final Rect visibleRect = + Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height); while (child != null) { final StackParentData childParentData = child.parentData; final Rect childRect = Rect.fromLTWH( @@ -1335,9 +1775,6 @@ class _MapRenderTileMarker extends RenderStack { childParentData.offset.dy + offset.dy, child.size.width, child.size.height); - final Rect visibleRect = - Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height); - if (visibleRect.overlaps(childRect)) { context.paintChild(child, offset + childParentData.offset); } diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart b/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart new file mode 100644 index 000000000..3f09f34b5 --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart @@ -0,0 +1,5436 @@ +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/theme.dart'; + +import '../../maps.dart'; +import '../behavior/zoom_pan_behavior.dart'; +import '../controller/default_controller.dart'; +import '../elements/shapes.dart'; +import '../enum.dart'; +import '../layer/layer_base.dart'; +import '../utils.dart'; + +double _getDesiredValue(double value, MapController controller) { + return controller.tileCurrentLevelDetails != null + ? value / controller.tileCurrentLevelDetails.scale + : value / + (controller.gesture == Gesture.scale ? controller.localScale : 1); +} + +Offset _getTranslationOffset(MapController controller, bool isTileLayer) { + return isTileLayer + ? -controller.tileCurrentLevelDetails.origin + : controller.shapeLayerOffset; +} + +Offset _updatePointsToScaledPosition(Offset point, MapController controller) { + if (controller.tileCurrentLevelDetails != null) { + return Offset(point.dx * controller.tileCurrentLevelDetails.scale, + point.dy * controller.tileCurrentLevelDetails.scale) + + controller.tileCurrentLevelDetails.translatePoint; + } + + return point; +} + +/// 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); +} + +/// A sublayer which renders group of [MapLine] on [MapShapeLayer] and +/// [MapTileLayer]. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ), +/// sublayers: [ +/// MapLineLayer( +/// lines: List.generate( +/// lines.length, +/// (int index) { +/// return MapLine( +/// from: lines[index].from, +/// to: lines[index].to, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], +/// ), +/// ); +/// } +/// ``` +/// +/// See also: +/// * [MapArcLayer], for adding arcs. +/// * [MapPolylineLayer], for polylines. +/// * [MapCircleLayer], for adding circles. +/// * [MapPolygonLayer], for adding polygons. +class MapLineLayer extends MapVectorLayer { + /// Creates the [MapLineLayer]. + MapLineLayer({ + Key key, + @required this.lines, + this.animation, + this.color, + this.width = 2, + IndexedWidgetBuilder tooltipBuilder, + }) : assert(lines != null), + super(key: key, tooltipBuilder: tooltipBuilder); + + /// A collection of [MapLine]. + /// + /// Every single [MapLine] connects two location coordinates through a + /// straight line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// lines.length, + /// (int index) { + /// return MapLine( + /// from: lines[index].from, + /// to: lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final Set lines; + + /// Animation for the [lines] in [MapLineLayer]. + /// + /// By default, [lines] will be rendered without any animation. The + /// animation can be set as shown in the below code snippet. You can customise + /// the animation flow, curve, duration and listen to the animation status. + /// + /// ```dart + /// AnimationController _animationController; + /// Animation _animation; + /// + /// @override + /// void initState() { + /// _animationController = AnimationController( + /// duration: Duration(seconds: 3), + /// vsync: this, + /// ); + /// + /// _animation = CurvedAnimation( + /// parent: _animationController, + /// curve: Curves.easeInOut), + /// ); + /// + /// _animationController.forward(from: 0); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// lines.length, + /// (int index) { + /// return MapLine( + /// from: lines[index].from, + /// to: lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// animation: _animation, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// @override + /// void dispose() { + /// animationController?.dispose(); + /// super.dispose(); + /// } + /// ``` + final Animation animation; + + /// The color of all the [lines]. + /// + /// For setting color for each [MapLine], please check the [MapLine.color] + /// property. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// lines.length, + /// (int index) { + /// return MapLine( + /// from: lines[index].from, + /// to: lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// color: Colors.green, + /// width: 2, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + /// + /// See also: + /// [width], to set the width. + final Color color; + + /// The width of all the [lines]. + /// + /// For setting width for each [MapLine], please check the [MapLine.width] + /// property. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// lines.length, + /// (int index) { + /// return MapLine( + /// from: lines[index].from, + /// to: lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// width: 2, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + /// + /// See also: + /// [color], to set the color. + final double width; + + @override + Widget build(BuildContext context) { + return _MapLineLayer( + lines: lines, + animation: animation, + color: color, + width: width, + tooltipBuilder: tooltipBuilder, + lineLayer: this, + ); + } +} + +class _MapLineLayer extends StatefulWidget { + _MapLineLayer({ + this.lines, + this.animation, + this.color, + this.width, + this.tooltipBuilder, + this.lineLayer, + }); + + final Set lines; + final Animation animation; + final Color color; + final double width; + final IndexedWidgetBuilder tooltipBuilder; + final MapLineLayer lineLayer; + + @override + _MapLineLayerState createState() => _MapLineLayerState(); +} + +class _MapLineLayerState extends State<_MapLineLayer> + with SingleTickerProviderStateMixin { + AnimationController _hoverAnimationController; + SfMapsThemeData _mapsThemeData; + + @override + void initState() { + super.initState(); + _hoverAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 250)); + } + + @override + void dispose() { + _hoverAnimationController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + final bool isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows; + _mapsThemeData = SfMapsTheme.of(context); + return _MapLineLayerRenderObject( + lines: widget.lines, + animation: widget.animation, + color: widget.color ?? + (_mapsThemeData.brightness == Brightness.light + ? const Color.fromRGBO(140, 140, 140, 1) + : const Color.fromRGBO(208, 208, 208, 1)), + width: widget.width, + tooltipBuilder: widget.tooltipBuilder, + lineLayer: widget.lineLayer, + themeData: _mapsThemeData, + isDesktop: isDesktop, + hoverAnimationController: _hoverAnimationController, + ); + } +} + +class _MapLineLayerRenderObject extends LeafRenderObjectWidget { + const _MapLineLayerRenderObject({ + this.lines, + this.animation, + this.color, + this.width, + this.tooltipBuilder, + this.lineLayer, + this.themeData, + this.isDesktop, + this.hoverAnimationController, + }); + + final Set lines; + final Animation animation; + final Color color; + final double width; + final IndexedWidgetBuilder tooltipBuilder; + final MapLineLayer lineLayer; + final SfMapsThemeData themeData; + final bool isDesktop; + final AnimationController hoverAnimationController; + + @override + _RenderMapLine createRenderObject(BuildContext context) { + return _RenderMapLine( + lines: lines, + animation: animation, + color: color, + width: width, + tooltipBuilder: tooltipBuilder, + context: context, + lineLayer: lineLayer, + themeData: themeData, + isDesktop: isDesktop, + hoverAnimationController: hoverAnimationController, + ); + } + + @override + void updateRenderObject(BuildContext context, _RenderMapLine renderObject) { + renderObject + ..lines = lines + .._animation = animation + ..color = color + ..width = width + ..tooltipBuilder = tooltipBuilder + ..context = context + ..lineLayer = lineLayer + ..themeData = themeData; + } +} + +class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { + _RenderMapLine({ + Set lines, + Animation animation, + Color color, + double width, + IndexedWidgetBuilder tooltipBuilder, + BuildContext context, + MapLineLayer lineLayer, + SfMapsThemeData themeData, + bool isDesktop, + AnimationController hoverAnimationController, + }) : _lines = lines, + _animation = animation, + _color = color, + _width = width, + _tooltipBuilder = tooltipBuilder, + context = context, + lineLayer = lineLayer, + _themeData = themeData, + isDesktop = isDesktop, + hoverAnimationController = hoverAnimationController { + selectedLinePoints = []; + _forwardHoverColor = ColorTween(); + _reverseHoverColor = ColorTween(); + _hoverColorAnimation = CurvedAnimation( + parent: hoverAnimationController, curve: Curves.easeInOut); + linesInList = _lines?.toList(); + _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; + } + + TapGestureRecognizer _tapGestureRecognizer; + MapController controller; + AnimationController hoverAnimationController; + RenderSublayerContainer vectorLayerContainer; + MapLineLayer lineLayer; + BuildContext context; + MapLine selectedLine; + int selectedIndex = -1; + double touchTolerance = 5; + List linesInList; + List selectedLinePoints; + Animation _hoverColorAnimation; + ColorTween _forwardHoverColor; + ColorTween _reverseHoverColor; + MapLine _previousHoverItem; + MapLine _currentHoverItem; + bool isDesktop; + + Set get lines => _lines; + Set _lines; + set lines(Set value) { + assert(value != null); + if (_lines == value || value == null) { + return; + } + _lines = value; + linesInList = _lines?.toList(); + markNeedsPaint(); + } + + Animation get animation => _animation; + Animation _animation; + set animation(Animation value) { + if (_animation == value) { + return; + } + _animation = value; + } + + IndexedWidgetBuilder get tooltipBuilder => _tooltipBuilder; + IndexedWidgetBuilder _tooltipBuilder; + set tooltipBuilder(IndexedWidgetBuilder value) { + if (_tooltipBuilder == value) { + return; + } + _tooltipBuilder = value; + } + + Color get color => _color; + Color _color; + set color(Color value) { + if (_color == value) { + return; + } + _color = value; + + // The previousHoverItem is not null when change the line color dynamically + // after hover on the [MapLine]. So reset the previousHoverItem here. + if (isDesktop && _previousHoverItem != null) { + _previousHoverItem = null; + } + markNeedsPaint(); + } + + double get width => _width; + double _width; + set width(double value) { + if (_width == value) { + return; + } + _width = value; + markNeedsPaint(); + } + + SfMapsThemeData get themeData => _themeData; + SfMapsThemeData _themeData; + set themeData(SfMapsThemeData value) { + if (_themeData == value) { + return; + } + _themeData = value; + markNeedsPaint(); + } + + void _updateHoverItemTween() { + if (isDesktop) { + final Color hoverStrokeColor = _getHoverColor(selectedLine); + final Color beginColor = selectedLine.color ?? _color; + + if (_previousHoverItem != null) { + _reverseHoverColor.begin = hoverStrokeColor; + _reverseHoverColor.end = beginColor; + } + + if (_currentHoverItem != null) { + _forwardHoverColor.begin = beginColor; + _forwardHoverColor.end = hoverStrokeColor; + } + hoverAnimationController.forward(from: 0); + } + } + + Color _getHoverColor(MapLine line) { + final Color color = line.color ?? _color; + final bool canAdjustHoverOpacity = + double.parse(color.opacity.toStringAsFixed(2)) != hoverColorOpacity; + return _themeData.shapeHoverColor != null && + _themeData.shapeHoverColor != Colors.transparent + ? _themeData.shapeHoverColor + : color.withOpacity( + canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); + } + + void _handleTapUp(TapUpDetails details) { + selectedLine.onTap?.call(); + _handleInteraction(details.localPosition); + } + + void _handlePointerExit(PointerExitEvent event) { + if (isDesktop && _currentHoverItem != null) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = null; + _updateHoverItemTween(); + } + + final RenderSublayerContainer vectorParent = parent; + final ShapeLayerChildRenderBoxBase tooltipRenderer = + vectorParent.tooltipKey?.currentContext?.findRenderObject(); + tooltipRenderer?.hideTooltip(); + } + + void _handleZooming(MapZoomDetails details) { + markNeedsPaint(); + } + + void _handlePanning(MapPanDetails details) { + markNeedsPaint(); + } + + void _handleReset() { + markNeedsPaint(); + } + + void _handleRefresh() { + markNeedsPaint(); + } + + void _handleInteraction(Offset position) { + final RenderSublayerContainer vectorParent = parent; + if (vectorParent.tooltipKey != null && _tooltipBuilder != null) { + final ShapeLayerChildRenderBoxBase tooltipRenderer = + vectorParent.tooltipKey.currentContext.findRenderObject(); + if (selectedLinePoints != null && selectedLinePoints.isNotEmpty) { + final Offset startPoint = selectedLinePoints[0]; + final Offset endPoint = selectedLinePoints[1]; + 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)); + position = + !paintBounds.contains(lineMidPosition) ? position : lineMidPosition; + tooltipRenderer.paintTooltip( + selectedIndex, + null, + MapLayerElement.vector, + vectorParent.getSublayerIndex(lineLayer), + position, + ); + } + } + } + + @override + MouseCursor get cursor => SystemMouseCursors.basic; + + @override + PointerEnterEventListener get onEnter => null; + + // As onHover property of MouseHoverAnnotation was removed only in the + // beta channel, once it is moved to stable, will remove this property. + @override + // ignore: override_on_non_overriding_member + PointerHoverEventListener get onHover => null; + + @override + PointerExitEventListener get onExit => _handlePointerExit; + + @override + // ignore: override_on_non_overriding_member + bool get validForMouseTracker => true; + + @override + bool hitTestSelf(Offset position) { + if (_animation != null && !_animation.isCompleted) { + return false; + } + + final bool isTileLayer = controller.tileCurrentLevelDetails != null; + final Size boxSize = isTileLayer ? controller.totalTileSize : size; + final Offset translationOffset = + _getTranslationOffset(controller, isTileLayer); + int index = linesInList.length - 1; + for (final MapLine line in linesInList?.reversed) { + final double width = line.width ?? _width; + if (line.onTap != null || _tooltipBuilder != null || isDesktop) { + final double actualTouchTolerance = + width < touchTolerance ? touchTolerance : width / 2; + Offset startPoint = pixelFromLatLng( + line.from.latitude, + line.from.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor, + ); + Offset endPoint = pixelFromLatLng( + line.to.latitude, + line.to.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor, + ); + startPoint = _updatePointsToScaledPosition(startPoint, controller); + endPoint = _updatePointsToScaledPosition(endPoint, controller); + + if (_liesPointOnLine( + startPoint, endPoint, actualTouchTolerance, position)) { + selectedLine = line; + selectedIndex = index; + selectedLinePoints + ..clear() + ..add(startPoint) + ..add(endPoint); + return true; + } + } + index--; + } + + return false; + } + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + if (event is PointerDownEvent) { + _tapGestureRecognizer.addPointer(event); + } else if (event is PointerHoverEvent) { + if (isDesktop && _currentHoverItem != selectedLine) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = selectedLine; + _updateHoverItemTween(); + } + + final RenderBox renderBox = context.findRenderObject(); + _handleInteraction(renderBox.globalToLocal(event.position)); + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + vectorLayerContainer = parent; + controller = vectorLayerContainer.controller; + if (controller != null) { + controller + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addResetListener(_handleReset) + ..addRefreshListener(_handleRefresh); + } + _animation?.addListener(markNeedsPaint); + _hoverColorAnimation?.addListener(markNeedsPaint); + } + + @override + void detach() { + if (controller != null) { + controller + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeResetListener(_handleReset) + ..removeRefreshListener(_handleRefresh); + } + _animation?.removeListener(markNeedsPaint); + _hoverColorAnimation?.removeListener(markNeedsPaint); + linesInList?.clear(); + linesInList = null; + super.detach(); + } + + @override + bool get isRepaintBoundary => true; + + @override + void performLayout() { + size = getBoxSize(constraints); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (_animation != null && _animation.value == 0.0) { + return; + } + context.canvas.save(); + Offset startPoint; + Offset endPoint; + final bool isTileLayer = controller.tileCurrentLevelDetails != null; + final Paint paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + Path path = Path(); + final Size boxSize = isTileLayer ? controller.totalTileSize : size; + final Offset translationOffset = + _getTranslationOffset(controller, isTileLayer); + controller.applyTransform(context, offset, true); + + for (final MapLine line in lines) { + startPoint = pixelFromLatLng( + line.from.latitude, + line.from.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor, + ); + endPoint = pixelFromLatLng( + line.to.latitude, + line.to.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor, + ); + + if (_previousHoverItem != null && + _previousHoverItem == line && + _themeData.shapeHoverColor != Colors.transparent) { + paint.color = _reverseHoverColor.evaluate(_hoverColorAnimation); + } else if (_currentHoverItem != null && + selectedLine == line && + _themeData.shapeHoverColor != Colors.transparent) { + paint.color = _forwardHoverColor.evaluate(_hoverColorAnimation); + } else { + paint.color = line.color ?? _color; + } + + paint.strokeWidth = _getDesiredValue(line.width ?? _width, controller); + path + ..reset() + ..moveTo(startPoint.dx, startPoint.dy) + ..lineTo(endPoint.dx, endPoint.dy); + if (_animation != null) { + path = _getAnimatedPath(path, _animation); + } + _drawDashedLine(context.canvas, line.dashArray, paint, path); + } + context.canvas.restore(); + } +} + +/// A sublayer which renders group of [MapArc] on [MapShapeLayer] and +/// [MapTileLayer]. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ), +/// sublayers: [ +/// MapArcLayer( +/// arcs: List.generate( +/// arcs.length, +/// (int index) { +/// return MapArc( +/// from: arcs[index].from, +/// to: arcs[index].to, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], +/// ), +/// ); +/// } +/// ``` +/// +/// See also: +/// * [MapLineLayer], for adding lines. +/// * [MapPolylineLayer], for polylines. +/// * [MapCircleLayer], for adding circles. +/// * [MapPolygonLayer], for adding polygons. +class MapArcLayer extends MapVectorLayer { + /// Creates the [MapArcLayer]. + MapArcLayer({ + Key key, + @required this.arcs, + this.animation, + this.color, + this.width = 2, + IndexedWidgetBuilder tooltipBuilder, + }) : assert(arcs != null), + super(key: key, tooltipBuilder: tooltipBuilder); + + /// A collection of [MapArc]. + /// + /// Every single [MapArc] connects two location coordinates through an + /// arc. The [MapArc.heightFactor] and [MapArc.controlPointFactor] can be + /// modified to change the appearance of the arcs. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// arcs.length, + /// (int index) { + /// return MapArc( + /// from: arcs[index].from, + /// to: arcs[index].to, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final Set arcs; + + /// Animation for the [arcs] in [MapArcLayer]. + /// + /// By default, [arcs] will be rendered without any animation. The + /// animation can be set as shown in the below code snippet. You can customise + /// the animation flow, curve, duration and listen to the animation status. + /// + /// ```dart + /// AnimationController _animationController; + /// Animation _animation; + /// + /// @override + /// void initState() { + /// _animationController = AnimationController( + /// duration: Duration(seconds: 3), + /// vsync: this, + /// ); + /// + /// _animation = CurvedAnimation( + /// parent: _animationController, + /// curve: Curves.easeInOut), + /// ); + /// + /// _animationController.forward(from: 0); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// arcs.length, + /// (int index) { + /// return MapArc( + /// from: arcs[index].from, + /// to: arcs[index].to, + /// ); + /// }, + /// ).toSet(), + /// animation: _animation, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// @override + /// void dispose() { + /// animationController?.dispose(); + /// super.dispose(); + /// } + /// ``` + final Animation animation; + + /// The color of all the [arcs]. + /// + /// For setting color for each [MapArc], please check the [MapArc.color] + /// property. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// arcs.length, + /// (int index) { + /// return MapArc( + /// from: arcs[index].from, + /// to: arcs[index].to, + /// ); + /// }, + /// ).toSet(), + /// color: Colors.green, + /// width: 2, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + /// + /// See also: + /// [width], to set the width for the map arc. + final Color color; + + /// The width of all the [arcs]. + /// + /// For setting width for each [MapArc], please check the [MapArc.width] + /// property. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// arcs.length, + /// (int index) { + /// return MapArc( + /// from: arcs[index].from, + /// to: arcs[index].to, + /// ); + /// }, + /// ).toSet(), + /// width: 2, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + /// See also: + /// [color], to set the color. + final double width; + + @override + Widget build(BuildContext context) { + return _MapArcLayer( + arcs: arcs, + animation: animation, + color: color, + width: width, + tooltipBuilder: tooltipBuilder, + arcLayer: this, + ); + } +} + +class _MapArcLayer extends StatefulWidget { + const _MapArcLayer({ + this.arcs, + this.animation, + this.color, + this.width, + this.tooltipBuilder, + this.arcLayer, + }); + + final Set arcs; + final Animation animation; + final Color color; + final double width; + final IndexedWidgetBuilder tooltipBuilder; + final MapArcLayer arcLayer; + + @override + _MapArcLayerState createState() => _MapArcLayerState(); +} + +class _MapArcLayerState extends State<_MapArcLayer> + with SingleTickerProviderStateMixin { + AnimationController _hoverAnimationController; + SfMapsThemeData _mapsThemeData; + + @override + void initState() { + super.initState(); + _hoverAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 250)); + } + + @override + void dispose() { + _hoverAnimationController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + final bool isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows; + _mapsThemeData = SfMapsTheme.of(context); + return _MapArcLayerRenderObject( + arcs: widget.arcs, + animation: widget.animation, + color: widget.color ?? + (_mapsThemeData.brightness == Brightness.light + ? const Color.fromRGBO(140, 140, 140, 1) + : const Color.fromRGBO(208, 208, 208, 1)), + width: widget.width, + tooltipBuilder: widget.tooltipBuilder, + arcLayer: widget.arcLayer, + themeData: _mapsThemeData, + isDesktop: isDesktop, + hoverAnimationController: _hoverAnimationController, + ); + } +} + +class _MapArcLayerRenderObject extends LeafRenderObjectWidget { + const _MapArcLayerRenderObject({ + this.arcs, + this.animation, + this.color, + this.width, + this.tooltipBuilder, + this.arcLayer, + this.themeData, + this.isDesktop, + this.hoverAnimationController, + }); + + final Set arcs; + final Animation animation; + final Color color; + final double width; + final IndexedWidgetBuilder tooltipBuilder; + final MapArcLayer arcLayer; + final SfMapsThemeData themeData; + final bool isDesktop; + final AnimationController hoverAnimationController; + + @override + _RenderMapArc createRenderObject(BuildContext context) { + return _RenderMapArc( + arcs: arcs, + animation: animation, + color: color, + width: width, + tooltipBuilder: tooltipBuilder, + context: context, + arcLayer: arcLayer, + themeData: themeData, + isDesktop: isDesktop, + hoverAnimationController: hoverAnimationController, + ); + } + + @override + void updateRenderObject(BuildContext context, _RenderMapArc renderObject) { + renderObject + ..arcs = arcs + ..animation = animation + ..color = color + ..width = width + ..tooltipBuilder = tooltipBuilder + ..context = context + ..arcLayer = arcLayer + ..themeData = themeData; + } +} + +class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { + _RenderMapArc({ + Set arcs, + Animation animation, + Color color, + double width, + IndexedWidgetBuilder tooltipBuilder, + BuildContext context, + MapArcLayer arcLayer, + SfMapsThemeData themeData, + bool isDesktop, + AnimationController hoverAnimationController, + }) : _arcs = arcs, + _color = color, + _width = width, + _animation = animation, + _tooltipBuilder = tooltipBuilder, + context = context, + arcLayer = arcLayer, + _themeData = themeData, + isDesktop = isDesktop, + hoverAnimationController = hoverAnimationController { + _forwardHoverColor = ColorTween(); + _reverseHoverColor = ColorTween(); + _hoverColorAnimation = CurvedAnimation( + parent: hoverAnimationController, curve: Curves.easeInOut); + arcsInList = _arcs?.toList(); + _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; + } + + TapGestureRecognizer _tapGestureRecognizer; + MapController controller; + RenderSublayerContainer vectorLayerContainer; + MapArcLayer arcLayer; + BuildContext context; + MapArc selectedArc; + int selectedIndex = -1; + double touchTolerance = 5; + List selectedLinePoints; + List arcsInList; + AnimationController hoverAnimationController; + Animation _hoverColorAnimation; + ColorTween _forwardHoverColor; + ColorTween _reverseHoverColor; + MapArc _previousHoverItem; + MapArc _currentHoverItem; + bool isDesktop; + + Set get arcs => _arcs; + Set _arcs; + set arcs(Set value) { + assert(value != null); + if (_arcs == value || value == null) { + return; + } + _arcs = value; + arcsInList = _arcs?.toList(); + markNeedsPaint(); + } + + IndexedWidgetBuilder get tooltipBuilder => _tooltipBuilder; + IndexedWidgetBuilder _tooltipBuilder; + set tooltipBuilder(IndexedWidgetBuilder value) { + if (_tooltipBuilder == value) { + return; + } + _tooltipBuilder = value; + } + + Color get color => _color; + Color _color; + set color(Color value) { + if (_color == value) { + return; + } + _color = value; + // The previousHoverItem is not null when change the arc color dynamically + // after hover on the [MapArc]. So reset the previousHoverItem here. + if (isDesktop && _previousHoverItem != null) { + _previousHoverItem = null; + } + markNeedsPaint(); + } + + double get width => _width; + double _width; + set width(double value) { + if (_width == value) { + return; + } + _width = value; + markNeedsPaint(); + } + + SfMapsThemeData get themeData => _themeData; + SfMapsThemeData _themeData; + set themeData(SfMapsThemeData value) { + if (_themeData == value) { + return; + } + _themeData = value; + markNeedsPaint(); + } + + Animation get animation => _animation; + Animation _animation; + set animation(Animation value) { + if (_animation == value) { + return; + } + _animation = value; + } + + void _updateHoverItemTween() { + if (isDesktop) { + final Color hoverStrokeColor = _getHoverColor(selectedArc); + final Color beginColor = selectedArc.color ?? _color; + + if (_previousHoverItem != null) { + _reverseHoverColor.begin = hoverStrokeColor; + _reverseHoverColor.end = beginColor; + } + + if (_currentHoverItem != null) { + _forwardHoverColor.begin = beginColor; + _forwardHoverColor.end = hoverStrokeColor; + } + hoverAnimationController.forward(from: 0); + } + } + + Color _getHoverColor(MapArc arc) { + final Color color = arc.color ?? _color; + final bool canAdjustHoverOpacity = + double.parse(color.opacity.toStringAsFixed(2)) != hoverColorOpacity; + return _themeData.shapeHoverColor != null && + _themeData.shapeHoverColor != Colors.transparent + ? _themeData.shapeHoverColor + : color.withOpacity( + canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); + } + + void _handleTapUp(TapUpDetails details) { + selectedArc.onTap?.call(); + _handleInteraction(details.localPosition); + } + + void _handlePointerExit(PointerExitEvent event) { + if (isDesktop && _currentHoverItem != null) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = null; + _updateHoverItemTween(); + } + + final RenderSublayerContainer vectorParent = parent; + final ShapeLayerChildRenderBoxBase tooltipRenderer = + vectorParent.tooltipKey?.currentContext?.findRenderObject(); + tooltipRenderer?.hideTooltip(); + } + + void _handleZooming(MapZoomDetails details) { + markNeedsPaint(); + } + + void _handlePanning(MapPanDetails details) { + markNeedsPaint(); + } + + void _handleReset() { + markNeedsPaint(); + } + + void _handleRefresh() { + markNeedsPaint(); + } + + @override + MouseCursor get cursor => SystemMouseCursors.basic; + + @override + PointerEnterEventListener get onEnter => null; + + // As onHover property of MouseHoverAnnotation was removed only in the + // beta channel, once it is moved to stable, will remove this property. + @override + // ignore: override_on_non_overriding_member + PointerHoverEventListener get onHover => null; + + @override + PointerExitEventListener get onExit => _handlePointerExit; + + @override + // ignore: override_on_non_overriding_member + bool get validForMouseTracker => true; + + void _handleInteraction(Offset position) { + final RenderSublayerContainer vectorParent = parent; + if (vectorParent.tooltipKey != null && _tooltipBuilder != null) { + final ShapeLayerChildRenderBoxBase tooltipRenderer = + vectorParent.tooltipKey.currentContext.findRenderObject(); + tooltipRenderer.paintTooltip( + selectedIndex, + null, + MapLayerElement.vector, + vectorParent.getSublayerIndex(arcLayer), + position, + ); + } + } + + @override + bool hitTestSelf(Offset position) { + if (_animation != null && !_animation.isCompleted) { + return false; + } + + final bool isTileLayer = controller.tileCurrentLevelDetails != null; + final Size boxSize = isTileLayer ? controller.totalTileSize : size; + final Offset translationOffset = + _getTranslationOffset(controller, isTileLayer); + int index = arcsInList.length - 1; + for (final MapArc arc in arcsInList?.reversed) { + final double width = arc.width ?? _width; + if (arc.onTap != null || _tooltipBuilder != null || isDesktop) { + final double actualTouchTolerance = + width < touchTolerance ? touchTolerance : width / 2; + Offset startPoint = pixelFromLatLng( + arc.from.latitude, + arc.from.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor, + ); + Offset endPoint = pixelFromLatLng( + arc.to.latitude, + arc.to.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor, + ); + startPoint = _updatePointsToScaledPosition(startPoint, controller); + endPoint = _updatePointsToScaledPosition(endPoint, controller); + final Offset controlPoint = _calculateControlPoint( + startPoint, endPoint, arc.heightFactor, arc.controlPointFactor); + + if (_liesPointOnArc(startPoint, endPoint, controlPoint, + actualTouchTolerance, position)) { + selectedArc = arc; + selectedIndex = index; + return true; + } + } + index--; + } + + return false; + } + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + if (event is PointerDownEvent) { + _tapGestureRecognizer.addPointer(event); + } else if (event is PointerHoverEvent) { + if (isDesktop && _currentHoverItem != selectedArc) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = selectedArc; + _updateHoverItemTween(); + } + + final RenderBox renderBox = context.findRenderObject(); + _handleInteraction(renderBox.globalToLocal(event.position)); + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + vectorLayerContainer = parent; + controller = vectorLayerContainer.controller; + if (controller != null) { + controller + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addResetListener(_handleReset) + ..addRefreshListener(_handleRefresh); + } + _animation?.addListener(markNeedsPaint); + _hoverColorAnimation?.addListener(markNeedsPaint); + } + + @override + void detach() { + if (controller != null) { + controller + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeResetListener(_handleReset) + ..removeRefreshListener(_handleRefresh); + } + _animation?.removeListener(markNeedsPaint); + _hoverColorAnimation?.removeListener(markNeedsPaint); + arcsInList?.clear(); + arcsInList = null; + super.detach(); + } + + @override + bool get isRepaintBoundary => true; + + @override + void performLayout() { + size = getBoxSize(constraints); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (_animation != null && _animation.value == 0) { + return; + } + context.canvas.save(); + Offset startPoint; + Offset endPoint; + final bool isTileLayer = controller.tileCurrentLevelDetails != null; + final Paint paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + Path path = Path(); + final Size boxSize = isTileLayer ? controller.totalTileSize : size; + final Offset translationOffset = + _getTranslationOffset(controller, isTileLayer); + controller.applyTransform(context, offset, true); + + for (final MapArc arc in arcs) { + startPoint = pixelFromLatLng( + arc.from.latitude, + arc.from.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor, + ); + endPoint = pixelFromLatLng( + arc.to.latitude, + arc.to.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor, + ); + final Offset controlPoint = _calculateControlPoint( + startPoint, endPoint, arc.heightFactor, arc.controlPointFactor); + + if (_previousHoverItem != null && + _previousHoverItem == arc && + _themeData.shapeHoverColor != Colors.transparent) { + paint.color = _reverseHoverColor.evaluate(_hoverColorAnimation); + } else if (_currentHoverItem != null && + selectedArc == arc && + _themeData.shapeHoverColor != Colors.transparent) { + paint.color = _forwardHoverColor.evaluate(_hoverColorAnimation); + } else { + paint.color = arc.color ?? _color; + } + + paint.strokeWidth = _getDesiredValue(arc.width ?? _width, controller); + path + ..reset() + ..moveTo(startPoint.dx, startPoint.dy) + ..quadraticBezierTo( + controlPoint.dx, controlPoint.dy, endPoint.dx, endPoint.dy); + if (_animation != null) { + path = _getAnimatedPath(path, _animation); + } + _drawDashedLine(context.canvas, arc.dashArray, paint, path); + } + context.canvas.restore(); + } + + 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 + // [MapArc.heightFactor]. + final double horizontalDistance = heightFactor * height; + final double verticalDistance = heightFactor * width; + + // Calculating curve bend point based on the [MapArc.controlPointFactor] + // 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)); + + if (startPoint.dx < endPoint.dx) { + controlPoint = Offset(controlPoint.dx + horizontalDistance, + controlPoint.dy - verticalDistance); + } else { + controlPoint = Offset(controlPoint.dx - horizontalDistance, + controlPoint.dy + verticalDistance); + } + return controlPoint; + } +} + +/// A sublayer which renders group of [MapPolyline] on [MapShapeLayer] and +/// [MapTileLayer]. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ), +/// sublayers: [ +/// MapPolylineLayer( +/// polylines: List.generate( +/// polylines.length, +/// (int index) { +/// return MapPolyline( +/// points: polylines[index].points, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], +/// ), +/// ); +/// } +/// ``` +/// +/// See also: +/// * [MapLineLayer], for adding lines. +/// * [MapArcLayer], for arcs. +/// * [MapCircleLayer], for adding circles. +/// * [MapPolygonLayer], for adding polygons. +class MapPolylineLayer extends MapVectorLayer { + /// Creates the [MapPolylineLayer]. + MapPolylineLayer({ + Key key, + @required this.polylines, + this.animation, + this.color, + this.width = 2, + IndexedWidgetBuilder tooltipBuilder, + }) : assert(polylines != null), + super(key: key, tooltipBuilder: tooltipBuilder); + + /// A collection of [MapPolyline]. + /// + /// Every single [MapPolyline] connects multiple location coordinates through + /// group of [MapPolyline.points]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// polylines.length, + /// (int index) { + /// return MapPolyline( + /// points: polylines[index].points, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final Set polylines; + + /// Animation for the [polylines] in [MapPolylineLayer]. + /// + /// By default, [polylines] will be rendered without any animation. The + /// animation can be set as shown in the below code snippet. You can customise + /// the animation flow, curve, duration and listen to the animation status. + /// + /// ```dart + /// AnimationController _animationController; + /// Animation _animation; + /// + /// @override + /// void initState() { + /// _animationController = AnimationController( + /// duration: Duration(seconds: 3), + /// vsync: this, + /// ); + /// + /// _animation = CurvedAnimation( + /// parent: _animationController, + /// curve: Curves.easeInOut), + /// ); + /// + /// _animationController.forward(from: 0); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// polylines.length, + /// (int index) { + /// return MapPolyline( + /// points: polylines[index].points, + /// ); + /// }, + /// ).toSet(), + /// animation: _animation, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// @override + /// void dispose() { + /// _animationController?.dispose(); + /// super.dispose(); + /// } + /// ``` + final Animation animation; + + /// The color of all the [polylines]. + /// + /// For setting color for each [MapPolyline], please check the + /// [MapPolyline.color] property. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// polylines.length, + /// (int index) { + /// return MapPolyline( + /// points: polylines[index].points, + /// ); + /// }, + /// ).toSet(), + /// color: Colors.green, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + /// + /// See also: + /// [width], for setting the width. + final Color color; + + /// The width of all the [polylines]. + /// + /// For setting width for each [MapPolyline], please check the + /// [MapPolyline.width] property. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// polylines.length, + /// (int index) { + /// return MapPolyline( + /// points: polylines[index].points, + /// ); + /// }, + /// ).toSet(), + /// color: Colors.green, + /// width: 5, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + /// + /// See also: + /// [color], for setting the color. + final double width; + + @override + Widget build(BuildContext context) { + return _MapPolylineLayer( + polylines: polylines, + animation: animation, + color: color, + width: width, + tooltipBuilder: tooltipBuilder, + polylineLayer: this, + ); + } +} + +class _MapPolylineLayer extends StatefulWidget { + const _MapPolylineLayer({ + this.polylines, + this.animation, + this.color, + this.width, + this.tooltipBuilder, + this.polylineLayer, + }); + + final Set polylines; + final Animation animation; + final Color color; + final double width; + final IndexedWidgetBuilder tooltipBuilder; + final MapPolylineLayer polylineLayer; + + @override + _MapPolylineLayerState createState() => _MapPolylineLayerState(); +} + +class _MapPolylineLayerState extends State<_MapPolylineLayer> + with SingleTickerProviderStateMixin { + AnimationController _hoverAnimationController; + SfMapsThemeData _mapsThemeData; + + @override + void initState() { + super.initState(); + _hoverAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 250)); + } + + @override + void dispose() { + _hoverAnimationController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + final bool isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows; + _mapsThemeData = SfMapsTheme.of(context); + return _MapPolylineLayerRenderObject( + polylines: widget.polylines, + animation: widget.animation, + color: widget.color ?? + (_mapsThemeData.brightness == Brightness.light + ? const Color.fromRGBO(140, 140, 140, 1) + : const Color.fromRGBO(208, 208, 208, 1)), + width: widget.width, + tooltipBuilder: widget.tooltipBuilder, + polylineLayer: widget.polylineLayer, + isDesktop: isDesktop, + themeData: _mapsThemeData, + hoverAnimationController: _hoverAnimationController, + ); + } +} + +class _MapPolylineLayerRenderObject extends LeafRenderObjectWidget { + const _MapPolylineLayerRenderObject({ + this.polylines, + this.animation, + this.color, + this.width, + this.tooltipBuilder, + this.polylineLayer, + this.themeData, + this.isDesktop, + this.hoverAnimationController, + }); + + final Set polylines; + final Animation animation; + final Color color; + final double width; + final IndexedWidgetBuilder tooltipBuilder; + final MapPolylineLayer polylineLayer; + final SfMapsThemeData themeData; + final bool isDesktop; + final AnimationController hoverAnimationController; + + @override + _RenderMapPolyline createRenderObject(BuildContext context) { + return _RenderMapPolyline( + polylines: polylines, + animation: animation, + color: color, + width: width, + tooltipBuilder: tooltipBuilder, + context: context, + polylineLayer: polylineLayer, + themeData: themeData, + isDesktop: isDesktop, + hoverAnimationController: hoverAnimationController, + ); + } + + @override + void updateRenderObject( + BuildContext context, _RenderMapPolyline renderObject) { + renderObject + ..polylines = polylines + ..animation = animation + ..color = color + ..width = width + ..tooltipBuilder = tooltipBuilder + ..context = context + ..polylineLayer = polylineLayer + ..themeData = themeData; + } +} + +class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { + _RenderMapPolyline({ + Set polylines, + Animation animation, + Color color, + double width, + IndexedWidgetBuilder tooltipBuilder, + BuildContext context, + MapPolylineLayer polylineLayer, + SfMapsThemeData themeData, + bool isDesktop, + AnimationController hoverAnimationController, + }) : _polylines = polylines, + _color = color, + _width = width, + _animation = animation, + _tooltipBuilder = tooltipBuilder, + context = context, + polylineLayer = polylineLayer, + _themeData = themeData, + isDesktop = isDesktop, + hoverAnimationController = hoverAnimationController { + _forwardHoverColor = ColorTween(); + _reverseHoverColor = ColorTween(); + _hoverColorAnimation = CurvedAnimation( + parent: hoverAnimationController, curve: Curves.easeInOut); + polylinesInList = _polylines?.toList(); + _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; + } + + TapGestureRecognizer _tapGestureRecognizer; + MapController controller; + RenderSublayerContainer vectorLayerContainer; + MapPolylineLayer polylineLayer; + BuildContext context; + MapPolyline selectedPolyline; + int selectedIndex = -1; + double touchTolerance = 5; + List polylinesInList; + AnimationController hoverAnimationController; + Animation _hoverColorAnimation; + ColorTween _forwardHoverColor; + ColorTween _reverseHoverColor; + MapPolyline _previousHoverItem; + MapPolyline _currentHoverItem; + bool isDesktop; + + Set get polylines => _polylines; + Set _polylines; + set polylines(Set value) { + assert(value != null); + if (_polylines == value || value == null) { + return; + } + _polylines = value; + polylinesInList = _polylines?.toList(); + markNeedsPaint(); + } + + Animation get animation => _animation; + Animation _animation; + set animation(Animation value) { + if (_animation == value) { + return; + } + _animation = value; + } + + IndexedWidgetBuilder get tooltipBuilder => _tooltipBuilder; + IndexedWidgetBuilder _tooltipBuilder; + set tooltipBuilder(IndexedWidgetBuilder value) { + if (_tooltipBuilder == value) { + return; + } + _tooltipBuilder = value; + } + + Color get color => _color; + Color _color; + set color(Color value) { + if (_color == value) { + return; + } + _color = value; + // The previousHoverItem is not null when change the polyline color + // dynamically after hover on the [MapPolyline]. So reset the + // previousHoverItem here. + if (isDesktop && _previousHoverItem != null) { + _previousHoverItem = null; + } + markNeedsPaint(); + } + + double get width => _width; + double _width; + set width(double value) { + if (_width == value) { + return; + } + _width = value; + markNeedsPaint(); + } + + SfMapsThemeData get themeData => _themeData; + SfMapsThemeData _themeData; + set themeData(SfMapsThemeData value) { + if (_themeData == value) { + return; + } + _themeData = value; + markNeedsPaint(); + } + + void _updateHoverItemTween() { + if (isDesktop) { + final Color hoverStrokeColor = _getHoverColor(selectedPolyline); + final Color beginColor = selectedPolyline.color ?? _color; + + if (_previousHoverItem != null) { + _reverseHoverColor.begin = hoverStrokeColor; + _reverseHoverColor.end = beginColor; + } + + if (_currentHoverItem != null) { + _forwardHoverColor.begin = beginColor; + _forwardHoverColor.end = hoverStrokeColor; + } + hoverAnimationController.forward(from: 0); + } + } + + Color _getHoverColor(MapPolyline polyline) { + final Color color = polyline.color ?? _color; + final bool canAdjustHoverOpacity = + double.parse(color.opacity.toStringAsFixed(2)) != hoverColorOpacity; + return _themeData.shapeHoverColor != null && + _themeData.shapeHoverColor != Colors.transparent + ? _themeData.shapeHoverColor + : color.withOpacity( + canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); + } + + void _handleTapUp(TapUpDetails details) { + selectedPolyline.onTap?.call(); + _handleInteraction(details.localPosition); + } + + void _handlePointerExit(PointerExitEvent event) { + if (isDesktop && _currentHoverItem != null) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = null; + _updateHoverItemTween(); + } + + final RenderSublayerContainer vectorParent = parent; + final ShapeLayerChildRenderBoxBase tooltipRenderer = + vectorParent.tooltipKey?.currentContext?.findRenderObject(); + tooltipRenderer?.hideTooltip(); + } + + void _handleZooming(MapZoomDetails details) { + markNeedsPaint(); + } + + void _handlePanning(MapPanDetails details) { + markNeedsPaint(); + } + + void _handleReset() { + markNeedsPaint(); + } + + void _handleRefresh() { + markNeedsPaint(); + } + + void _handleInteraction(Offset position) { + final RenderSublayerContainer vectorParent = parent; + if (vectorParent.tooltipKey != null && _tooltipBuilder != null) { + final ShapeLayerChildRenderBoxBase tooltipRenderer = + vectorParent.tooltipKey.currentContext.findRenderObject(); + tooltipRenderer.paintTooltip( + selectedIndex, + null, + MapLayerElement.vector, + vectorParent.getSublayerIndex(polylineLayer), + position, + ); + } + } + + @override + MouseCursor get cursor => SystemMouseCursors.basic; + + @override + PointerEnterEventListener get onEnter => null; + + // As onHover property of MouseHoverAnnotation was removed only in the + // beta channel, once it is moved to stable, will remove this property. + @override + // ignore: override_on_non_overriding_member + PointerHoverEventListener get onHover => null; + + @override + PointerExitEventListener get onExit => _handlePointerExit; + + @override + // ignore: override_on_non_overriding_member + bool get validForMouseTracker => true; + + @override + bool hitTestSelf(Offset position) { + if (_animation != null && !_animation.isCompleted) { + return false; + } + + final bool isTileLayer = controller.tileCurrentLevelDetails != null; + final Size boxSize = isTileLayer ? controller.totalTileSize : size; + final Offset translationOffset = + _getTranslationOffset(controller, isTileLayer); + bool tappedOnLine = false; + int index = polylinesInList.length - 1; + for (final MapPolyline polyline in polylinesInList?.reversed) { + if (tappedOnLine) { + return true; + } + final double width = polyline.width ?? _width; + if (polyline.onTap != null || _tooltipBuilder != null || isDesktop) { + final double actualTouchTolerance = + width < touchTolerance ? touchTolerance : width / 2; + + for (int j = 0; j < polyline.points.length - 1; j++) { + final MapLatLng currentPoint = polyline.points[j]; + final MapLatLng nextPoint = polyline.points[j + 1]; + Offset startPoint = pixelFromLatLng( + currentPoint.latitude, + currentPoint.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor, + ); + Offset endPoint = pixelFromLatLng( + nextPoint.latitude, + nextPoint.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor, + ); + startPoint = _updatePointsToScaledPosition(startPoint, controller); + endPoint = _updatePointsToScaledPosition(endPoint, controller); + + if (_liesPointOnLine( + startPoint, endPoint, actualTouchTolerance, position)) { + tappedOnLine = true; + selectedPolyline = polyline; + selectedIndex = index; + return true; + } + } + } + index--; + } + + return false; + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + vectorLayerContainer = parent; + controller = vectorLayerContainer.controller; + if (controller != null) { + controller + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addResetListener(_handleReset) + ..addRefreshListener(_handleRefresh); + } + _animation?.addListener(markNeedsPaint); + _hoverColorAnimation?.addListener(markNeedsPaint); + } + + @override + void detach() { + if (controller != null) { + controller + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeResetListener(_handleReset) + ..removeRefreshListener(_handleRefresh); + } + _animation?.removeListener(markNeedsPaint); + _hoverColorAnimation?.removeListener(markNeedsPaint); + polylinesInList?.clear(); + polylinesInList = null; + super.detach(); + } + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + if (event is PointerDownEvent) { + _tapGestureRecognizer.addPointer(event); + } else if (event is PointerHoverEvent) { + if (isDesktop && _currentHoverItem != selectedPolyline) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = selectedPolyline; + _updateHoverItemTween(); + } + + final RenderBox renderBox = context.findRenderObject(); + _handleInteraction(renderBox.globalToLocal(event.position)); + } + } + + @override + bool get isRepaintBoundary => true; + + @override + void performLayout() { + size = getBoxSize(constraints); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (_animation != null && _animation.value == 0.0) { + return; + } + context.canvas.save(); + final Paint paint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + Path path = Path(); + final bool isTileLayer = controller.tileCurrentLevelDetails != null; + final Size boxSize = isTileLayer ? controller.totalTileSize : size; + final Offset translationOffset = + _getTranslationOffset(controller, isTileLayer); + controller.applyTransform(context, offset, true); + for (final MapPolyline polyline in polylines) { + if (polyline.points != null) { + final MapLatLng startCoordinate = polyline.points[0]; + final Offset startPoint = pixelFromLatLng( + startCoordinate.latitude, + startCoordinate.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor); + path + ..reset() + ..moveTo(startPoint.dx, startPoint.dy); + + for (int j = 1; j < polyline.points.length; j++) { + final MapLatLng nextCoordinate = polyline.points[j]; + final Offset nextPoint = pixelFromLatLng( + nextCoordinate.latitude, + nextCoordinate.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor); + path.lineTo(nextPoint.dx, nextPoint.dy); + } + + if (_previousHoverItem != null && + _previousHoverItem == polyline && + _themeData.shapeHoverColor != Colors.transparent) { + paint.color = _reverseHoverColor.evaluate(_hoverColorAnimation); + } else if (_currentHoverItem != null && + selectedPolyline == polyline && + _themeData.shapeHoverColor != Colors.transparent) { + paint.color = _forwardHoverColor.evaluate(_hoverColorAnimation); + } else { + paint.color = polyline.color ?? _color; + } + + paint.strokeWidth = + _getDesiredValue(polyline.width ?? _width, controller); + if (_animation != null) { + path = _getAnimatedPath(path, _animation); + } + _drawDashedLine(context.canvas, polyline.dashArray, paint, path); + } + } + context.canvas.restore(); + } +} + +/// A sublayer which renders group of [MapPolygon] on [MapShapeLayer] and +/// [MapTileLayer]. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ), +/// sublayers: [ +/// MapPolygonLayer( +/// polygons: List.generate( +/// polygons.length, +/// (int index) { +/// return MapPolygon( +/// points: polygons[index].points, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], +/// ), +/// ); +/// } +/// ``` +class MapPolygonLayer extends MapVectorLayer { + /// Creates the [MapPolygonLayer]. + MapPolygonLayer({ + Key key, + @required this.polygons, + this.color = const Color.fromRGBO(51, 153, 144, 1), + this.strokeWidth = 1, + this.strokeColor = const Color.fromRGBO(51, 153, 144, 1), + IndexedWidgetBuilder tooltipBuilder, + }) : assert(polygons != null), + super(key: key, tooltipBuilder: tooltipBuilder); + + /// A collection of [MapPolygon]. + /// + /// Every single [MapPolygon] is a closed path which connects multiple + /// location coordinates through group of [MapPolygon.points]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolygonLayer( + /// polygons: List.generate( + /// polygons.length, + /// (int index) { + /// return MapPolygon( + /// points: polygons[index].points, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final Set polygons; + + /// The fill color of all the [MapPolygon]. + /// + /// For setting color for each [MapPolygon], please check the + /// [MapPolygon.color] property. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolygonLayer( + /// polygons: List.generate( + /// polygons.length, + /// (int index) { + /// return MapPolygon( + /// points: polygons[index].points, + /// ); + /// }, + /// ).toSet(), + /// color: Colors.red, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final Color color; + + /// The stroke width of all the [MapPolygon]. + /// + /// For setting stroke width for each [MapPolygon], please check the + /// [MapPolygon.strokeWidth] property. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolygonLayer( + /// polygons: List.generate( + /// polygons.length, + /// (int index) { + /// return MapPolygon( + /// points: polygons[index].points, + /// ); + /// }, + /// ).toSet(), + /// strokeColor: Colors.red, + /// strokeWidth: 5, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + /// See also: + /// [strokeColor], to set the stroke color for the polygon. + final double strokeWidth; + + /// The stroke color of all the [MapPolygon]. + /// + /// For setting stroke color for each [MapPolygon], please check the + /// [MapPolygon.strokeColor] property. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolygonLayer( + /// polygons: List.generate( + /// polygons.length, + /// (int index) { + /// return MapPolygon( + /// points: polygons[index].points, + /// ); + /// }, + /// ).toSet(), + /// strokeColor: Colors.red, + /// strokeWidth: 5, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + /// + /// See also: + /// [strokeWidth], to set the stroke width for the polygon. + final Color strokeColor; + + @override + Widget build(BuildContext context) { + return _MapPolygonLayer( + polygons: polygons, + color: color, + strokeWidth: strokeWidth, + strokeColor: strokeColor, + tooltipBuilder: tooltipBuilder, + polygonLayer: this, + ); + } +} + +class _MapPolygonLayer extends StatefulWidget { + const _MapPolygonLayer({ + this.polygons, + this.color, + this.strokeWidth, + this.strokeColor, + this.tooltipBuilder, + this.polygonLayer, + }); + + final Set polygons; + final Color color; + final double strokeWidth; + final Color strokeColor; + final IndexedWidgetBuilder tooltipBuilder; + final MapPolygonLayer polygonLayer; + + @override + _MapPolygonLayerState createState() => _MapPolygonLayerState(); +} + +class _MapPolygonLayerState extends State<_MapPolygonLayer> + with SingleTickerProviderStateMixin { + AnimationController _hoverAnimationController; + SfMapsThemeData _mapsThemeData; + + @override + void initState() { + super.initState(); + _hoverAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 250)); + } + + @override + void dispose() { + _hoverAnimationController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + final bool isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows; + _mapsThemeData = SfMapsTheme.of(context); + return _MapPolygonLayerRenderObject( + polygons: widget.polygons, + color: widget.color, + strokeWidth: widget.strokeWidth, + strokeColor: widget.strokeColor, + tooltipBuilder: widget.tooltipBuilder, + polygonLayer: widget.polygonLayer, + hoverAnimationController: _hoverAnimationController, + themeData: _mapsThemeData, + isDesktop: isDesktop, + ); + } +} + +class _MapPolygonLayerRenderObject extends LeafRenderObjectWidget { + const _MapPolygonLayerRenderObject({ + this.polygons, + this.color, + this.strokeWidth, + this.strokeColor, + this.tooltipBuilder, + this.polygonLayer, + this.hoverAnimationController, + this.themeData, + this.isDesktop, + }); + + final Set polygons; + final Color color; + final double strokeWidth; + final Color strokeColor; + final IndexedWidgetBuilder tooltipBuilder; + final MapPolygonLayer polygonLayer; + final AnimationController hoverAnimationController; + final SfMapsThemeData themeData; + final bool isDesktop; + + @override + _RenderMapPolygon createRenderObject(BuildContext context) { + return _RenderMapPolygon( + polygons: polygons, + color: color, + strokeColor: strokeColor, + strokeWidth: strokeWidth, + tooltipBuilder: tooltipBuilder, + themeData: themeData, + context: context, + polygonLayer: polygonLayer, + hoverAnimationController: hoverAnimationController, + isDesktop: isDesktop, + ); + } + + @override + void updateRenderObject( + BuildContext context, _RenderMapPolygon renderObject) { + renderObject + ..polygons = polygons + ..color = color + ..strokeColor = strokeColor + ..strokeWidth = strokeWidth + ..tooltipBuilder = tooltipBuilder + ..themeData = themeData + ..context = context + ..polygonLayer = polygonLayer; + } +} + +class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { + _RenderMapPolygon({ + Set polygons, + Color color, + double strokeWidth, + Color strokeColor, + IndexedWidgetBuilder tooltipBuilder, + SfMapsThemeData themeData, + BuildContext context, + MapPolygonLayer polygonLayer, + AnimationController hoverAnimationController, + bool isDesktop, + }) : _polygons = polygons, + _color = color, + _strokeWidth = strokeWidth, + _strokeColor = strokeColor, + _tooltipBuilder = tooltipBuilder, + _themeData = themeData, + context = context, + polygonLayer = polygonLayer, + hoverAnimationController = hoverAnimationController, + isDesktop = isDesktop { + _forwardHoverColor = ColorTween(); + _reverseHoverColor = ColorTween(); + _forwardHoverStrokeColor = ColorTween(); + _reverseHoverStrokeColor = ColorTween(); + _hoverColorAnimation = CurvedAnimation( + parent: hoverAnimationController, curve: Curves.easeInOut); + _polygonsInList = _polygons?.toList(); + _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; + } + + MapController controller; + AnimationController hoverAnimationController; + RenderSublayerContainer vectorLayerContainer; + bool isDesktop; + MapPolygonLayer polygonLayer; + BuildContext context; + TapGestureRecognizer _tapGestureRecognizer; + Animation _hoverColorAnimation; + ColorTween _forwardHoverColor; + ColorTween _reverseHoverColor; + ColorTween _forwardHoverStrokeColor; + ColorTween _reverseHoverStrokeColor; + MapPolygon _previousHoverItem; + MapPolygon _currentHoverItem; + List _polygonsInList; + int _selectedIndex = -1; + MapPolygon _selectedPolygon; + + Set get polygons => _polygons; + Set _polygons; + set polygons(Set value) { + assert(value != null); + if (_polygons == value || value == null) { + return; + } + _polygons = value; + _polygonsInList = _polygons?.toList(); + markNeedsPaint(); + } + + IndexedWidgetBuilder get tooltipBuilder => _tooltipBuilder; + IndexedWidgetBuilder _tooltipBuilder; + set tooltipBuilder(IndexedWidgetBuilder value) { + if (_tooltipBuilder == value) { + return; + } + _tooltipBuilder = value; + } + + SfMapsThemeData get themeData => _themeData; + SfMapsThemeData _themeData; + set themeData(SfMapsThemeData value) { + if (_themeData == value) { + return; + } + _themeData = value; + markNeedsPaint(); + } + + Color get color => _color; + Color _color; + set color(Color value) { + if (_color == value) { + return; + } + _color = value; + // The previousHoverItem is not null when change the polygon color + // dynamically after hover on the [MapPolygon]. So reset the + // previousHoverItem here. + if (isDesktop && _previousHoverItem != null) { + _previousHoverItem = null; + } + markNeedsPaint(); + } + + double get strokeWidth => _strokeWidth; + double _strokeWidth; + set strokeWidth(double value) { + if (_strokeWidth == value) { + return; + } + _strokeWidth = value; + markNeedsPaint(); + } + + Color get strokeColor => _strokeColor; + Color _strokeColor; + set strokeColor(Color value) { + if (_strokeColor == value) { + return; + } + _strokeColor = value; + // The previousHoverItem is not null when change the polygon stroke color + // dynamically after hover on the [MapPolygon]. So reset the + // previousHoverItem here. + if (isDesktop && _previousHoverItem != null) { + _previousHoverItem = null; + } + markNeedsPaint(); + } + + bool get canHover => hasHoverColor || hasHoverStrokeColor; + + bool get hasHoverColor => + isDesktop && + _themeData.shapeHoverColor != null && + _themeData.shapeHoverColor != Colors.transparent; + + bool get hasHoverStrokeColor => + isDesktop && + _themeData.shapeHoverStrokeColor != null && + _themeData.shapeHoverStrokeColor != Colors.transparent; + + void _initializeHoverItemTween() { + if (isDesktop) { + final Color hoverFillColor = _getHoverFillColor(_selectedPolygon); + final Color hoverStrokeColor = _getHoverStrokeColor(_selectedPolygon); + final Color defaultFillColor = _selectedPolygon.color ?? _color; + final Color defaultStrokeColor = + _selectedPolygon.strokeColor ?? _strokeColor; + + if (_previousHoverItem != null) { + _reverseHoverColor.begin = hoverFillColor; + _reverseHoverColor.end = defaultFillColor; + _reverseHoverStrokeColor.begin = hoverStrokeColor; + _reverseHoverStrokeColor.end = defaultStrokeColor; + } + + if (_currentHoverItem != null) { + _forwardHoverColor.begin = defaultFillColor; + _forwardHoverColor.end = hoverFillColor; + _forwardHoverStrokeColor.begin = defaultStrokeColor; + _forwardHoverStrokeColor.end = hoverStrokeColor; + } + hoverAnimationController.forward(from: 0); + } + } + + Color _getHoverFillColor(MapPolygon polygon) { + if (hasHoverColor) { + return _themeData.shapeHoverColor; + } + final Color color = polygon.color ?? _color; + return color.withOpacity( + (double.parse(color.opacity.toStringAsFixed(2)) != hoverColorOpacity) + ? hoverColorOpacity + : minHoverOpacity); + } + + Color _getHoverStrokeColor(MapPolygon polygon) { + if (hasHoverStrokeColor) { + return _themeData.shapeHoverStrokeColor; + } + final Color strokeColor = polygon.strokeColor ?? _strokeColor; + return strokeColor.withOpacity( + (double.parse(strokeColor.opacity.toStringAsFixed(2)) != + hoverColorOpacity) + ? hoverColorOpacity + : minHoverOpacity); + } + + void _handlePointerExit(PointerExitEvent event) { + if (isDesktop && _currentHoverItem != null) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = null; + _initializeHoverItemTween(); + } + final RenderSublayerContainer vectorParent = parent; + final ShapeLayerChildRenderBoxBase tooltipRenderer = + vectorParent.tooltipKey?.currentContext?.findRenderObject(); + tooltipRenderer?.hideTooltip(); + } + + void _handleZooming(MapZoomDetails details) { + markNeedsPaint(); + } + + void _handlePanning(MapPanDetails details) { + markNeedsPaint(); + } + + void _handleReset() { + markNeedsPaint(); + } + + void _handleRefresh() { + markNeedsPaint(); + } + + void _handleInteraction(Offset position) { + final RenderSublayerContainer vectorParent = parent; + if (vectorParent.tooltipKey != null && _tooltipBuilder != null) { + final ShapeLayerChildRenderBoxBase tooltipRenderer = + vectorParent.tooltipKey.currentContext.findRenderObject(); + tooltipRenderer.paintTooltip(_selectedIndex, null, MapLayerElement.vector, + vectorParent.getSublayerIndex(polygonLayer), position); + } + } + + void _handleTapUp(TapUpDetails details) { + _selectedPolygon.onTap?.call(); + _handleInteraction(details.localPosition); + } + + @override + MouseCursor get cursor => SystemMouseCursors.basic; + + @override + PointerEnterEventListener get onEnter => null; + + // As onHover property of MouseHoverAnnotation was removed only in the + // beta channel, once it is moved to stable, will remove this property. + @override + // ignore: override_on_non_overriding_member + PointerHoverEventListener get onHover => null; + + @override + PointerExitEventListener get onExit => _handlePointerExit; + + @override + // ignore: override_on_non_overriding_member + bool get validForMouseTracker => true; + + @override + bool hitTestSelf(Offset position) { + int index = _polygonsInList.length - 1; + final bool isTileLayer = controller.tileCurrentLevelDetails != null; + final Size boxSize = isTileLayer ? controller.totalTileSize : size; + final Offset translationOffset = + _getTranslationOffset(controller, isTileLayer); + for (final MapPolygon polygon in _polygonsInList?.reversed) { + if (polygon.onTap != null || _tooltipBuilder != null || canHover) { + final Path path = Path(); + if (polygon.points != null) { + final MapLatLng startCoordinate = polygon.points[0]; + final Offset startPoint = pixelFromLatLng( + startCoordinate.latitude, + startCoordinate.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor); + path.moveTo(startPoint.dx, startPoint.dy); + + for (int j = 1; j < polygon.points.length; j++) { + final MapLatLng nextCoordinate = polygon.points[j]; + final Offset nextPoint = pixelFromLatLng( + nextCoordinate.latitude, + nextCoordinate.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor); + path.lineTo(nextPoint.dx, nextPoint.dy); + } + path.close(); + if (path.contains(position)) { + _selectedPolygon = polygon; + _selectedIndex = index; + return true; + } + } + } + index--; + } + + return false; + } + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + if (event is PointerDownEvent) { + _tapGestureRecognizer.addPointer(event); + } else if (event is PointerHoverEvent && isDesktop) { + if (_currentHoverItem != _selectedPolygon) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = _selectedPolygon; + _initializeHoverItemTween(); + } + + final RenderBox renderBox = context.findRenderObject(); + _handleInteraction(renderBox.globalToLocal(event.position)); + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + vectorLayerContainer = parent; + controller = vectorLayerContainer.controller; + if (controller != null) { + controller + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addResetListener(_handleReset) + ..addRefreshListener(_handleRefresh); + } + _hoverColorAnimation?.addListener(markNeedsPaint); + } + + @override + void detach() { + if (controller != null) { + controller + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeResetListener(_handleReset) + ..removeRefreshListener(_handleRefresh); + } + _hoverColorAnimation?.removeListener(markNeedsPaint); + _polygonsInList?.clear(); + _polygonsInList = null; + super.detach(); + } + + @override + bool get isRepaintBoundary => true; + + @override + void performLayout() { + size = getBoxSize(constraints); + } + + @override + 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 bool isTileLayer = controller.tileCurrentLevelDetails != null; + final Size boxSize = isTileLayer ? controller.totalTileSize : size; + final Offset translationOffset = + _getTranslationOffset(controller, isTileLayer); + controller.applyTransform(context, offset, true); + + for (final MapPolygon polygon in polygons) { + final Path path = Path(); + if (polygon.points != null) { + final MapLatLng startLatLng = polygon.points[0]; + final Offset startPoint = pixelFromLatLng( + startLatLng.latitude, + startLatLng.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor); + path.moveTo(startPoint.dx, startPoint.dy); + + for (int j = 1; j < polygon.points.length; j++) { + final MapLatLng nextCoordinate = polygon.points[j]; + final Offset nextPoint = pixelFromLatLng( + nextCoordinate.latitude, + nextCoordinate.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor); + path.lineTo(nextPoint.dx, nextPoint.dy); + } + path.close(); + _updateFillColor(polygon, fillPaint); + _updateStroke(polygon, strokePaint); + context.canvas..drawPath(path, fillPaint)..drawPath(path, strokePaint); + } + } + context.canvas.restore(); + } + + void _updateFillColor(MapPolygon polygon, Paint paint) { + if (!isDesktop) { + paint.color = polygon.color ?? _color; + return; + } + + if (_previousHoverItem != null && _previousHoverItem == polygon) { + 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); + } else { + paint.color = polygon.color ?? _color; + } + } + + void _updateStroke(MapPolygon polygon, Paint paint) { + if (!isDesktop) { + _updateDefaultStroke(paint, polygon); + return; + } + + if (_previousHoverItem != null && _previousHoverItem == polygon) { + _updateHoverStrokeColor(paint, polygon, _reverseHoverStrokeColor); + paint.strokeWidth = + _getDesiredValue(polygon.strokeWidth ?? _strokeWidth, controller); + } else if (_currentHoverItem != null && _currentHoverItem == polygon) { + _updateHoverStrokeColor(paint, polygon, _forwardHoverStrokeColor); + if (_themeData.shapeHoverStrokeWidth != null && + _themeData.shapeHoverStrokeWidth > 0.0) { + paint.strokeWidth = + _getDesiredValue(_themeData.shapeHoverStrokeWidth, controller); + } else { + paint.strokeWidth = + _getDesiredValue(polygon.strokeWidth ?? _strokeWidth, controller); + } + } else { + _updateDefaultStroke(paint, polygon); + } + } + + void _updateDefaultStroke(Paint paint, MapPolygon polygon) { + paint + ..color = polygon.strokeColor ?? _strokeColor + ..strokeWidth = + _getDesiredValue(polygon.strokeWidth ?? _strokeWidth, controller); + } + + void _updateHoverStrokeColor( + Paint paint, MapPolygon polygon, ColorTween tween) { + if (_themeData.shapeHoverStrokeColor != Colors.transparent) { + paint.color = tween.evaluate(_hoverColorAnimation); + } else { + paint.color = polygon.strokeColor ?? _strokeColor; + } + } +} + +/// A sublayer which renders group of [MapCircle] on [MapShapeLayer] and +/// [MapTileLayer]. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ), +/// sublayers: [ +/// MapCircleLayer( +/// circles: List.generate( +/// circles.length, +/// (int index) { +/// return MapCircle( +/// center: circles[index], +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], +/// ), +/// ); +/// } +/// ``` +class MapCircleLayer extends MapVectorLayer { + /// Creates the [MapCircleLayer]. + MapCircleLayer({ + Key key, + @required this.circles, + this.animation, + this.color = const Color.fromRGBO(51, 153, 144, 1), + this.strokeWidth = 1, + this.strokeColor = const Color.fromRGBO(51, 153, 144, 1), + IndexedWidgetBuilder tooltipBuilder, + }) : assert(circles != null), + super(key: key, tooltipBuilder: tooltipBuilder); + + /// A collection of [MapCircle]. + /// + /// Every single [MapCircle] is drawn based on the given [MapCircle.center] + /// and [MapCircle.radius]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// circles.length, + /// (int index) { + /// return MapCircle( + /// center: circles[index], + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final Set circles; + + /// Animation for the [circles] in [MapPolylineLayer]. + /// + /// By default, [circles] will be rendered without any animation. The + /// animation can be set as shown in the below code snippet. You can customise + /// the animation flow, curve, duration and listen to the animation status. + /// + /// ```dart + /// AnimationController _animationController; + /// Animation _animation; + /// + /// @override + /// void initState() { + /// _animationController = AnimationController( + /// duration: Duration(seconds: 3), + /// vsync: this, + /// ); + /// + /// _animation = CurvedAnimation( + /// parent: _animationController, + /// curve: Curves.easeInOut), + /// ); + /// + /// _animationController.forward(from: 0); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// circles.length, + /// (int index) { + /// return MapCircle( + /// center: circles[index], + /// ); + /// }, + /// ).toSet(), + /// animation: _animation, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// @override + /// void dispose() { + /// _animationController?.dispose(); + /// super.dispose(); + /// } + /// + /// ``` + final Animation animation; + + /// The fill color of all the [MapCircle]. + /// + /// For setting color for each [MapCircle], please check the + /// [MapCircle.color] property. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// circles.length, + /// (int index) { + /// return MapCircle( + /// center: circles[index], + /// ); + /// }, + /// ).toSet(), + /// color: Colors.red, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final Color color; + + /// The stroke width of all the [MapCircle]. + /// + /// For setting stroke width for each [MapCircle], please check the + /// [MapCircle.strokeWidth] property. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// circles.length, + /// (int index) { + /// return MapCircle( + /// center: circles[index], + /// ); + /// }, + /// ).toSet(), + /// strokeWidth: 4, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + /// See also: + /// [strokeColor], to set the stroke color for the circles. + final double strokeWidth; + + /// The stroke color of all the [MapCircle]. + /// + /// For setting stroke color for each [MapCircle], please check the + /// [MapCircle.strokeColor] property. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// circles.length, + /// (int index) { + /// return MapCircle( + /// center: circles[index], + /// ); + /// }, + /// ).toSet(), + /// strokeWidth: 4, + /// strokeColor: Colors.red, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + /// + /// See also: + /// [strokeWidth], to set the stroke width for the circles. + final Color strokeColor; + + @override + Widget build(BuildContext context) { + return _MapCircleLayer( + circles: circles, + animation: animation, + color: color, + strokeColor: strokeColor, + strokeWidth: strokeWidth, + tooltipBuilder: tooltipBuilder, + circleLayer: this, + ); + } +} + +class _MapCircleLayer extends StatefulWidget { + const _MapCircleLayer({ + this.circles, + this.animation, + this.color, + this.strokeWidth, + this.strokeColor, + this.tooltipBuilder, + this.circleLayer, + }); + + final Set circles; + final Animation animation; + final Color color; + final double strokeWidth; + final Color strokeColor; + final IndexedWidgetBuilder tooltipBuilder; + final MapCircleLayer circleLayer; + + @override + _MapCircleLayerState createState() => _MapCircleLayerState(); +} + +class _MapCircleLayerState extends State<_MapCircleLayer> + with SingleTickerProviderStateMixin { + AnimationController _hoverAnimationController; + SfMapsThemeData _mapsThemeData; + + @override + void initState() { + super.initState(); + _hoverAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 250)); + } + + @override + void dispose() { + _hoverAnimationController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + final bool isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows; + _mapsThemeData = SfMapsTheme.of(context); + return _MapCircleLayerRenderObject( + circles: widget.circles, + animation: widget.animation, + color: widget.color, + strokeWidth: widget.strokeWidth, + strokeColor: widget.strokeColor, + tooltipBuilder: widget.tooltipBuilder, + circleLayer: widget.circleLayer, + hoverAnimationController: _hoverAnimationController, + themeData: _mapsThemeData, + isDesktop: isDesktop, + ); + } +} + +class _MapCircleLayerRenderObject extends LeafRenderObjectWidget { + const _MapCircleLayerRenderObject({ + this.circles, + this.animation, + this.color, + this.strokeWidth, + this.strokeColor, + this.tooltipBuilder, + this.circleLayer, + this.hoverAnimationController, + this.themeData, + this.isDesktop, + }); + + final Set circles; + final Animation animation; + final Color color; + final double strokeWidth; + final Color strokeColor; + final IndexedWidgetBuilder tooltipBuilder; + final MapCircleLayer circleLayer; + final AnimationController hoverAnimationController; + final SfMapsThemeData themeData; + final bool isDesktop; + + @override + _RenderMapCircle createRenderObject(BuildContext context) { + return _RenderMapCircle( + circles: circles, + animation: animation, + color: color, + strokeColor: strokeColor, + strokeWidth: strokeWidth, + tooltipBuilder: tooltipBuilder, + themeData: themeData, + context: context, + circleLayer: circleLayer, + hoverAnimationController: hoverAnimationController, + isDesktop: isDesktop, + ); + } + + @override + void updateRenderObject(BuildContext context, _RenderMapCircle renderObject) { + renderObject + ..circles = circles + ..animation = animation + ..color = color + ..strokeColor = strokeColor + ..strokeWidth = strokeWidth + ..tooltipBuilder = tooltipBuilder + ..themeData = themeData + ..context = context + ..circleLayer = circleLayer; + } +} + +class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { + _RenderMapCircle({ + Set circles, + Animation animation, + Color color, + double strokeWidth, + Color strokeColor, + IndexedWidgetBuilder tooltipBuilder, + SfMapsThemeData themeData, + BuildContext context, + MapCircleLayer circleLayer, + AnimationController hoverAnimationController, + bool isDesktop, + }) : _circles = circles, + _animation = animation, + _color = color, + _strokeColor = strokeColor, + _strokeWidth = strokeWidth, + _tooltipBuilder = tooltipBuilder, + _themeData = themeData, + context = context, + circleLayer = circleLayer, + hoverAnimationController = hoverAnimationController, + isDesktop = isDesktop { + _forwardHoverColor = ColorTween(); + _reverseHoverColor = ColorTween(); + _forwardHoverStrokeColor = ColorTween(); + _reverseHoverStrokeColor = ColorTween(); + _hoverColorAnimation = CurvedAnimation( + parent: hoverAnimationController, curve: Curves.easeInOut); + _circlesInList = _circles?.toList(); + _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; + } + + MapController controller; + AnimationController hoverAnimationController; + RenderSublayerContainer vectorLayerContainer; + MapCircleLayer circleLayer; + bool isDesktop; + BuildContext context; + TapGestureRecognizer _tapGestureRecognizer; + Animation _hoverColorAnimation; + ColorTween _forwardHoverColor; + ColorTween _reverseHoverColor; + ColorTween _forwardHoverStrokeColor; + ColorTween _reverseHoverStrokeColor; + MapCircle _previousHoverItem; + MapCircle _currentHoverItem; + int _selectedIndex = -1; + MapCircle _selectedCircle; + List _circlesInList; + + Set get circles => _circles; + Set _circles; + set circles(Set value) { + assert(value != null); + if (_circles == value || value == null) { + return; + } + _circles = value; + _circlesInList = _circles?.toList(); + markNeedsPaint(); + } + + Animation get animation => _animation; + Animation _animation; + set animation(Animation value) { + if (_animation == value) { + return; + } + _animation = value; + } + + IndexedWidgetBuilder get tooltipBuilder => _tooltipBuilder; + IndexedWidgetBuilder _tooltipBuilder; + set tooltipBuilder(IndexedWidgetBuilder value) { + if (_tooltipBuilder == value) { + return; + } + _tooltipBuilder = value; + } + + SfMapsThemeData get themeData => _themeData; + SfMapsThemeData _themeData; + set themeData(SfMapsThemeData value) { + if (_themeData == value) { + return; + } + _themeData = value; + markNeedsPaint(); + } + + Color get color => _color; + Color _color; + set color(Color value) { + if (_color == value) { + return; + } + _color = value; + // The previousHoverItem is not null when change the polygon color + // dynamically after hover on the [MapCircle]. So reset the + // previousHoverItem here. + if (isDesktop && _previousHoverItem != null) { + _previousHoverItem = null; + } + markNeedsPaint(); + } + + double get strokeWidth => _strokeWidth; + double _strokeWidth; + set strokeWidth(double value) { + if (_strokeWidth == value) { + return; + } + _strokeWidth = value; + markNeedsPaint(); + } + + Color get strokeColor => _strokeColor; + Color _strokeColor; + set strokeColor(Color value) { + if (_strokeColor == value) { + return; + } + _strokeColor = value; + // The previousHoverItem is not null when change the polygon stroke color + // dynamically after hover on the [MapCircle]. So reset the + // previousHoverItem here. + if (isDesktop && _previousHoverItem != null) { + _previousHoverItem = null; + } + markNeedsPaint(); + } + + bool get canHover => hasHoverColor || hasHoverStrokeColor; + + bool get hasHoverColor => + isDesktop && + _themeData.shapeHoverColor != null && + _themeData.shapeHoverColor != Colors.transparent; + + bool get hasHoverStrokeColor => + isDesktop && + _themeData.shapeHoverStrokeColor != null && + _themeData.shapeHoverStrokeColor != Colors.transparent; + + void _initializeHoverItemTween() { + if (isDesktop) { + final Color hoverFillColor = _getHoverFillColor(_selectedCircle); + final Color hoverStrokeColor = _getHoverStrokeColor(_selectedCircle); + final Color defaultFillColor = _selectedCircle.color ?? _color; + final Color defaultStrokeColor = + _selectedCircle.strokeColor ?? _strokeColor; + + if (_previousHoverItem != null) { + _reverseHoverColor.begin = hoverFillColor; + _reverseHoverColor.end = defaultFillColor; + _reverseHoverStrokeColor.begin = hoverStrokeColor; + _reverseHoverStrokeColor.end = defaultStrokeColor; + } + + if (_currentHoverItem != null) { + _forwardHoverColor.begin = defaultFillColor; + _forwardHoverColor.end = hoverFillColor; + _forwardHoverStrokeColor.begin = defaultStrokeColor; + _forwardHoverStrokeColor.end = hoverStrokeColor; + } + hoverAnimationController.forward(from: 0); + } + } + + Color _getHoverFillColor(MapCircle circle) { + if (hasHoverColor) { + return _themeData.shapeHoverColor; + } + final Color color = circle.color ?? _color; + return color.withOpacity( + (double.parse(color.opacity.toStringAsFixed(2)) != hoverColorOpacity) + ? hoverColorOpacity + : minHoverOpacity); + } + + Color _getHoverStrokeColor(MapCircle circle) { + if (hasHoverStrokeColor) { + return _themeData.shapeHoverStrokeColor; + } + final Color strokeColor = circle.strokeColor ?? _strokeColor; + return strokeColor.withOpacity( + (double.parse(strokeColor.opacity.toStringAsFixed(2)) != + hoverColorOpacity) + ? hoverColorOpacity + : minHoverOpacity); + } + + void _handlePointerExit(PointerExitEvent event) { + if (isDesktop && _currentHoverItem != null) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = null; + _initializeHoverItemTween(); + } + + final RenderSublayerContainer vectorParent = parent; + final ShapeLayerChildRenderBoxBase tooltipRenderer = + vectorParent.tooltipKey?.currentContext?.findRenderObject(); + tooltipRenderer?.hideTooltip(); + } + + void _handleZooming(MapZoomDetails details) { + markNeedsPaint(); + } + + void _handlePanning(MapPanDetails details) { + markNeedsPaint(); + } + + void _handleReset() { + markNeedsPaint(); + } + + void _handleRefresh() { + markNeedsPaint(); + } + + void _handleInteraction(Offset position) { + final RenderSublayerContainer vectorParent = parent; + if (vectorParent.tooltipKey != null && _tooltipBuilder != null) { + final ShapeLayerChildRenderBoxBase tooltipRenderer = + vectorParent.tooltipKey.currentContext.findRenderObject(); + final bool isTileLayer = controller.tileCurrentLevelDetails != null; + final Size boxSize = isTileLayer ? controller.totalTileSize : size; + final Offset translationOffset = + _getTranslationOffset(controller, isTileLayer); + + if (_selectedIndex != -1) { + final Offset center = pixelFromLatLng( + _selectedCircle.center.latitude, + _selectedCircle.center.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor, + ); + + final Rect circleRect = + Rect.fromCircle(center: center, radius: _selectedCircle.radius); + tooltipRenderer.paintTooltip( + _selectedIndex, + circleRect, + MapLayerElement.vector, + vectorParent.getSublayerIndex(circleLayer), + position, + ); + } + } + } + + void _handleTapUp(TapUpDetails details) { + _selectedCircle.onTap?.call(); + _handleInteraction(details.localPosition); + } + + @override + MouseCursor get cursor => SystemMouseCursors.basic; + + @override + PointerEnterEventListener get onEnter => null; + + // As onHover property of MouseHoverAnnotation was removed only in the + // beta channel, once it is moved to stable, will remove this property. + @override + // ignore: override_on_non_overriding_member + PointerHoverEventListener get onHover => null; + + @override + PointerExitEventListener get onExit => _handlePointerExit; + + @override + // ignore: override_on_non_overriding_member + bool get validForMouseTracker => true; + + @override + bool hitTestSelf(Offset position) { + if (_animation != null && !_animation.isCompleted) { + return false; + } + + int index = _circlesInList.length - 1; + final bool isTileLayer = controller.tileCurrentLevelDetails != null; + final Size boxSize = isTileLayer ? controller.totalTileSize : size; + final Offset translationOffset = + _getTranslationOffset(controller, isTileLayer); + + for (final MapCircle circle in _circlesInList?.reversed) { + if (circle.onTap != null || _tooltipBuilder != null || canHover) { + final Path path = Path() + ..addOval(Rect.fromCircle( + center: pixelFromLatLng( + circle.center.latitude, + circle.center.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor, + ), + radius: circle.radius, + )); + if (path.contains(position)) { + _selectedCircle = circle; + _selectedIndex = index; + return true; + } + } + index--; + } + return false; + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + vectorLayerContainer = parent; + controller = vectorLayerContainer.controller; + if (controller != null) { + controller + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addResetListener(_handleReset) + ..addRefreshListener(_handleRefresh); + } + _animation?.addListener(markNeedsPaint); + _hoverColorAnimation?.addListener(markNeedsPaint); + } + + @override + void detach() { + if (controller != null) { + controller + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeResetListener(_handleReset) + ..removeRefreshListener(_handleRefresh); + } + _animation?.removeListener(markNeedsPaint); + _hoverColorAnimation?.removeListener(markNeedsPaint); + _circlesInList?.clear(); + _circlesInList = null; + super.detach(); + } + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + if (event is PointerDownEvent) { + _tapGestureRecognizer.addPointer(event); + } else if (event is PointerHoverEvent && isDesktop) { + if (_currentHoverItem != _selectedCircle) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = _selectedCircle; + _initializeHoverItemTween(); + } + + final RenderBox renderBox = context.findRenderObject(); + _handleInteraction(renderBox.globalToLocal(event.position)); + } + } + + @override + bool get isRepaintBoundary => true; + + @override + void performLayout() { + size = getBoxSize(constraints); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (_animation != null && _animation.value == 0.0) { + return; + } + context.canvas.save(); + final Paint fillPaint = Paint()..isAntiAlias = true; + final Paint strokePaint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + final bool isTileLayer = controller.tileCurrentLevelDetails != null; + final Size boxSize = isTileLayer ? controller.totalTileSize : size; + final Offset translationOffset = + _getTranslationOffset(controller, isTileLayer); + controller.applyTransform(context, offset, true); + + for (final MapCircle circle in circles) { + final Path path = Path(); + path + ..addOval(Rect.fromCircle( + center: pixelFromLatLng( + circle.center.latitude, + circle.center.longitude, + boxSize, + translationOffset, + controller.shapeLayerSizeFactor, + ), + radius: _getDesiredValue( + circle.radius * (_animation?.value ?? 1.0), controller), + )); + _updateFillColor(circle, fillPaint); + _updateStroke(circle, strokePaint); + context.canvas..drawPath(path, fillPaint)..drawPath(path, strokePaint); + } + context.canvas.restore(); + } + + void _updateFillColor(MapCircle circle, Paint paint) { + if (!isDesktop) { + paint.color = circle.color ?? _color; + return; + } + + if (_previousHoverItem != null && _previousHoverItem == circle) { + 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); + } else { + paint.color = circle.color ?? _color; + } + } + + void _updateStroke(MapCircle circle, Paint paint) { + if (!isDesktop) { + _updateDefaultStroke(paint, circle); + return; + } + + if (_previousHoverItem != null && _previousHoverItem == circle) { + _updateHoverStrokeColor(paint, circle, _reverseHoverStrokeColor); + paint.strokeWidth = + _getDesiredValue(circle.strokeWidth ?? _strokeWidth, controller); + } else if (_currentHoverItem != null && _currentHoverItem == circle) { + _updateHoverStrokeColor(paint, circle, _forwardHoverStrokeColor); + if (_themeData.shapeHoverStrokeWidth != null && + _themeData.shapeHoverStrokeWidth > 0.0) { + paint.strokeWidth = + _getDesiredValue(_themeData.shapeHoverStrokeWidth, controller); + } else { + paint.strokeWidth = + _getDesiredValue(circle.strokeWidth ?? _strokeWidth, controller); + } + } else { + _updateDefaultStroke(paint, circle); + } + } + + void _updateDefaultStroke(Paint paint, MapCircle circle) { + paint + ..color = circle.strokeColor ?? _strokeColor + ..strokeWidth = + _getDesiredValue(circle.strokeWidth ?? _strokeWidth, controller); + } + + void _updateHoverStrokeColor( + Paint paint, MapCircle circle, ColorTween tween) { + if (_themeData.shapeHoverStrokeColor != Colors.transparent) { + paint.color = tween.evaluate(_hoverColorAnimation); + } else { + paint.color = circle.strokeColor ?? _strokeColor; + } + } +} + +/// Creates a line between the two geographical coordinates on the map. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ), +/// sublayers: [ +/// MapLineLayer( +/// lines: List.generate( +/// lines.length, +/// (int index) { +/// return MapLine( +/// from: lines[index].from, +/// to: lines[index].to, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], +/// ), +/// ); +/// } +/// ``` +class MapLine { + /// Creates a [MapLine]. + const MapLine({ + @required this.from, + @required this.to, + this.dashArray = const [0, 0], + this.color, + this.width, + this.onTap, + }); + + /// The starting coordinate of the line. + /// + ///```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// lines.length, + /// (int index) { + /// return MapLine( + /// from: lines[index].from, + /// to: lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final MapLatLng from; + + /// The ending coordinate of the line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// lines.length, + /// (int index) { + /// return MapLine( + /// from: lines[index].from, + /// to: lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final MapLatLng to; + + /// The dash pattern for the line. + /// + /// A sequence of dash and gap will be rendered based on the values in this + /// list. Once all values of the list is rendered, it will be repeated + /// again till the end of the line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// lines.length, + /// (int index) { + /// return MapLine( + /// from: lines[index].from, + /// to: lines[index].to, + /// dashArray: [8, 3, 4, 3], + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final List dashArray; + + /// Color of the line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// lines.length, + /// (int index) { + /// return MapLine( + /// from: lines[index].from, + /// to: lines[index].to, + /// color: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final Color color; + + /// Width of the line. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// lines.length, + /// (int index) { + /// return MapLine( + /// from: lines[index].from, + /// to: lines[index].to, + /// width: 4, + /// color: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final double width; + + /// Callback to receive tap event for this line. + /// + /// You can customize the appearance of the tapped line based on the index + /// passed on it as shown in the below code snippet. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// lines.length, + /// (int index) { + /// return MapLine( + /// from: lines[index].from, + /// to: lines[index].to, + /// color: _selectedIndex == index + /// ? Colors.blue + /// : Colors.red, + /// onTap: () { + /// setState(() { + /// _selectedIndex = index; + /// }); + /// }, + /// ); + /// }, + /// ).toSet(), + /// ), + // ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final VoidCallback onTap; +} + +/// Creates a polyline by connecting multiple geographical coordinates through +/// group of [points]. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ), +/// sublayers: [ +/// MapPolylineLayer( +/// polylines: List.generate( +/// polylines.length, +/// (int index) { +/// return MapPolyline( +/// points: polylines[index].points, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], +/// ), +/// ); +/// } +/// ``` +class MapPolyline { + /// Creates a [MapPolyline]. + const MapPolyline({ + @required this.points, + this.dashArray = const [0, 0], + this.color, + this.width, + this.onTap, + }); + + /// The geolocation coordinates of the polyline to be drawn. + /// + /// Lines are drawn between consecutive [points]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// lines.length, + /// (int index) { + /// return MapPolyline( + /// points: lines[index].points, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final List points; + + /// The dash pattern for the polyline. + /// + /// A sequence of dash and gap will be rendered based on the values in this + /// list. Once all values of the list is rendered, it will be repeated + /// again till the end of the polyline. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// lines.length, + /// (int index) { + /// return MapPolyline( + /// points: lines[index].points, + /// dashArray: [8, 3, 4, 3], + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final List dashArray; + + /// Color of the polyline. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// lines.length, + /// (int index) { + /// return MapPolyline( + /// points: lines[index].points, + /// color: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final Color color; + + /// Width of the polyline. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// lines.length, + /// (int index) { + /// return MapPolyline( + /// points: lines[index].points, + /// color: Colors.blue, + /// width: 4, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final double width; + + /// Callback to receive tap event for this polyline. + /// + /// You can customize the appearance of the tapped polyline based on the + /// index passed in it as shown in the below code snippet. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// lines.length, + /// (int index) { + /// return MapPolyline( + /// points: lines[index].points, + /// color: _selectedIndex == index + /// ? Colors.blue + /// : Colors.red, + /// onTap: () { + /// setState(() { + /// _selectedIndex = index; + /// }); + /// }, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final VoidCallback onTap; +} + +/// Creates a closed path which connects multiple geographical coordinates +/// through group of [MapPolygon.points]. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ), +/// sublayers: [ +/// MapPolygonLayer( +/// polygons: List.generate( +/// polygons.length, +/// (int index) { +/// return MapPolygon( +/// points: polygons[index].points, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], +/// ), +/// ); +/// } +/// ``` +class MapPolygon { + /// Creates a [MapPolygon]. + const MapPolygon({ + @required this.points, + this.color, + this.strokeColor, + this.strokeWidth, + this.onTap, + }); + + /// The geolocation coordinates of the polygon to be drawn. + /// + /// Lines are drawn between consecutive [points] to form a closed shape. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolygonLayer( + /// polygons: List.generate( + /// polygons.length, + /// (int index) { + /// return MapPolygon( + /// points: polygons[index].points, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final List points; + + /// Specifies the fill color of the polygon. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolygonLayer( + /// polygons: List.generate( + /// polygons.length, + /// (int index) { + /// return MapPolygon( + /// points: polygons[index].points, + /// color: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final Color color; + + /// Specifies the stroke color of the polygon. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolygonLayer( + /// polygons: List.generate( + /// polygons.length, + /// (int index) { + /// return MapPolygon( + /// points: polygons[index].points, + /// strokeColor: Colors.red, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final Color strokeColor; + + /// Specifies the stroke width of the polygon. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolygonLayer( + /// polygons: List.generate( + /// polygons.length, + /// (int index) { + /// return MapPolygon( + /// points: polygons[index].points, + /// strokeWidth: 4, + /// strokeColor: Colors.red, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final double strokeWidth; + + /// Callback to receive tap event for this polygon. + /// + /// You can customize the appearance of the tapped polygon based on the index + /// passed on it as shown in the below code snippet. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapPolygonLayer( + /// polygons: List.generate( + /// polygons.length, + /// (int index) { + /// return MapPolygon( + /// points: polygons[index].points, + /// color: _selectedIndex == index + /// ? Colors.blue + /// : Colors.red, + /// onTap: () { + /// setState(() { + /// _selectedIndex = index; + /// }); + /// }, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + ///} + final VoidCallback onTap; +} + +/// Creates a circle which is drawn based on the given [center] and +/// [radius]. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ), +/// sublayers: [ +/// MapCircleLayer( +/// circles: List.generate( +/// circles.length, +/// (int index) { +/// return MapCircle( +/// center: circles[index], +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], +/// ), +/// ); +/// } +/// ``` +class MapCircle { + /// Creates a [MapCircle]. + const MapCircle({ + @required this.center, + this.radius = 5, + this.color, + this.strokeColor, + this.strokeWidth, + this.onTap, + }); + + /// The center of the circle. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// circles.length, + /// (int index) { + /// return MapCircle( + /// center: circles[index], + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final MapLatLng center; + + /// The radius of the circle. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// circles.length, + /// (int index) { + /// return MapCircle( + /// center: circles[index], + /// radius: 20, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final double radius; + + /// The fill color of the circle. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// circles.length, + /// (int index) { + /// return MapCircle( + /// center: circles[index], + /// color: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final Color color; + + /// Stroke width of the circle. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// circles.length, + /// (int index) { + /// return MapCircle( + /// center: circles[index], + /// strokeWidth: 4, + /// strokeColor: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final double strokeWidth; + + /// Stroke color of the circle. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// circles.length, + /// (int index) { + /// return MapCircle( + /// center: circles[index], + /// strokeColor: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final Color strokeColor; + + /// Callback to receive tap event for this circle. + /// + /// You can customize the appearance of the tapped circle based on the index + /// passed on it as shown in the below code snippet. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// circles.length, + /// (int index) { + /// return MapCircle( + /// center: circles[index], + /// color: _selectedIndex == index + /// ? Colors.blue + /// : Colors.red, + /// onTap: () { + /// setState(() { + /// _selectedIndex = index; + /// }); + /// }, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final VoidCallback onTap; +} + +/// Creates an arc by connecting the two geographical coordinates. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ), +/// sublayers: [ +/// MapArcLayer( +/// arcs: List.generate( +/// arcs.length, +/// (int index) { +/// return MapArc( +/// from: arcs[index].from, +/// to: arcs[index].to, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], +/// ), +/// ); +/// } +/// ``` +class MapArc { + /// Creates a [MapArc]. + const MapArc({ + @required this.from, + @required this.to, + this.heightFactor = 0.2, + this.controlPointFactor = 0.5, + this.dashArray = const [0, 0], + this.color, + this.width, + this.onTap, + }); + + /// Represents the start coordinate of an arc. + /// + ///```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ), + /// subLayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// arcs.length, + /// (int index) { + /// return MapArc( + /// from: arcs[index].from, + /// to: arcs[index].to, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final MapLatLng from; + + /// Represents the end coordinate of an arc. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ), + /// subLayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// arcs.length, + /// (int index) { + /// return MapArc( + /// from: arcs[index].from, + /// to: arcs[index].to, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final MapLatLng to; + + /// Specifies the distance from the line connecting two points to the arc + /// bend point. + /// + /// Defaults to 0.2. + /// + /// The value ranges from -1 to 1. + /// + /// By default, the arc will always render above the [from] and [to] points. + /// To render the arc below the points, set the value between -1 to 0. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ), + /// subLayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// arcs.length, + /// (int index) { + /// return MapArc( + /// from: arcs[index].from, + /// to: arcs[index].to, + /// heightFactor: 0.6, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final double heightFactor; + + /// Specifies the arc bending position. + /// + /// Defaults to 0.5. + /// + /// The arc will bend at the center between the [from] and [to] points by + /// default. + /// + /// The value ranges from 0 to 1. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ), + /// subLayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// arcs.length, + /// (int index) { + /// return MapArc( + /// from: arcs[index].from, + /// to: arcs[index].to, + /// controlPointFactor: 0.4, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final double controlPointFactor; + + /// The dash pattern for the arc. + /// + /// A sequence of dash and gap will be rendered based on the values in this + /// list. Once all values of the list is rendered, it will be repeated + /// again till the end of the arc. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// subLayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// arcs.length, + /// (int index) { + /// return MapArc( + /// from: arcs[index].from, + /// to: arcs[index].to, + /// dashArray: [8, 3, 4, 3], + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final List dashArray; + + /// Color of the arc. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// subLayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// arcs.length, + /// (int index) { + /// return MapArc( + /// from: arcs[index].from, + /// to: arcs[index].to, + /// color: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final Color color; + + /// Width of the arc. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// subLayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// arcs.length, + /// (int index) { + /// return MapArc( + /// from: arcs[index].from, + /// to: arcs[index].to, + /// width: 4, + /// color: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final double width; + + /// Callback to receive tap event for this arc. + /// + /// You can customize the appearance of the tapped arc based on the index + /// passed on it as shown in the below code snippet. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ), + /// subLayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// arcs.length, + /// (int index) { + /// return MapArc( + /// from: arcs[index].from, + /// to: arcs[index].to, + /// color: _selectedIndex == index + /// ? Colors.blue + /// : Colors.red, + /// onTap: () { + /// setState(() { + /// _selectedIndex = index; + /// }); + /// }, + /// ); + /// }, + /// ).toSet(), + /// ), + // ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + final VoidCallback onTap; +} + +// 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; +} + +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) { + paint.isAntiAlias = false; + canvas.drawPath( + _dashPath( + path, + dashArray: _IntervalList(dashArray), + ), + paint); + } else { + canvas.drawPath(path, paint); + } +} + +// 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++]; + } +} + +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; + final double height = endPoint.dy - startPoint.dy; + final double lineLength = sqrt(width * width + height * height); + 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 lineBottomRight = Offset( + endPoint.dx + horizontalTouchLength, endPoint.dy - verticalTouchLength); + final Offset lineBottomLeft = Offset( + endPoint.dx - horizontalTouchLength, endPoint.dy + verticalTouchLength); + path + ..moveTo(lineTopLeft.dx, lineTopLeft.dy) + ..lineTo(lineTopRight.dx, lineTopRight.dy) + ..lineTo(lineBottomRight.dx, lineBottomRight.dy) + ..lineTo(lineBottomLeft.dx, lineBottomLeft.dy) + ..close(); + return path.contains(touchPosition); +} + +Path _getAnimatedPath(Path originalPath, Animation animation) { + final Path extractedPath = Path(); + double currentLength = 0.0; + final PathMetrics pathMetrics = originalPath.computeMetrics(); + final double pathLength = pathMetrics.fold( + 0.0, + (double previousValue, PathMetric pathMetric) => + previousValue + pathMetric.length); + final double requiredPathLength = pathLength * animation.value; + final Iterator metricsIterator = + originalPath.computeMetrics().iterator; + + while (metricsIterator.moveNext()) { + final PathMetric metric = metricsIterator.current; + final double nextLength = currentLength + metric.length; + if (nextLength > requiredPathLength) { + extractedPath.addPath( + metric.extractPath(0.0, requiredPathLength - currentLength), + Offset.zero); + break; + } + extractedPath.addPath(metric.extractPath(0.0, metric.length), Offset.zero); + currentLength = nextLength; + } + + return extractedPath; +} + +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; + // Calculate distance between two points i.e, d = sqrt[(x1-x2)^2+(y1-y2)^2]. + final double lineLength = sqrt(width * width + height * height); + 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 lineTopRight = Offset( + 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); + final Offset controlPointBottom = Offset( + controlPoint.dx - horizontalTouchLength, + controlPoint.dy + verticalTouchLength); + path + ..moveTo(lineTopLeft.dx, lineTopLeft.dy) + ..quadraticBezierTo(controlPointTop.dx, controlPointTop.dy, lineTopRight.dx, + lineTopRight.dy) + ..lineTo(lineBottomRight.dx, lineBottomRight.dy) + ..quadraticBezierTo(controlPointBottom.dx, controlPointBottom.dy, + lineBottomLeft.dx, lineBottomLeft.dy) + ..close(); + return path.contains(touchPosition); +} diff --git a/packages/syncfusion_flutter_maps/lib/src/maps.dart b/packages/syncfusion_flutter_maps/lib/src/maps.dart deleted file mode 100644 index 0d2a73027..000000000 --- a/packages/syncfusion_flutter_maps/lib/src/maps.dart +++ /dev/null @@ -1,269 +0,0 @@ -part of maps; - -/// Renders the GeoJSON data as a geographical area. -/// -/// The maps has the following elements and features, -/// -/// * The "data labels", to provide information to users about the respective -/// shape. -/// * The "markers", which denotes a location with built-in symbols and allows -/// displaying custom widgets at a specific latitude and longitude on a map. -/// * The "bubbles", which adds information to shapes such as population -/// density, number of users, and more. Bubbles can be rendered in different -/// colors and sizes based on the data values of their assigned shape. -/// * The "legend", to provide clear information on the data plotted in the map. -/// You can use the legend toggling feature to visualize only the shapes to -/// which needs to be visualized. -/// * The "color mapping", to categorize the shapes on a map by customizing -/// their color based on the underlying value. It is possible to set the shape -/// color for a specific value or for a range of values. -/// * The "tooltip", to display additional information about shapes and bubbles -/// using the customizable tooltip on a map. -/// * Along with this, the selection feature helps to highlight that area on a -/// map on interaction. You can use the callback for performing any action -/// during shape selection. -/// -/// The [SfMaps.layers] is a collection of [MapLayer]. It contains either -/// [MapShapeLayer] and [MapTileLayer]. -/// -/// ## Shape layer -/// -/// The actual geographical rendering is done in each [MapShapeLayer] using the -/// [MapShapeLayer.delegate]. The path of the .json file which contains the -/// GeoJSON data has to be set to the [MapShapeLayerDelegate.shapeFile]. -/// -/// The [MapShapeLayerDelegate.shapeDataField] property is used to -/// refer the unique field name in the .json file to identify each shapes and -/// map with the respective data in the data source. -/// -/// By default, the value specified for the -/// [MapShapeLayerDelegate.shapeDataField] in the GeoJSON file will be used in -/// the elements like data labels, tooltip, and legend for their respective -/// shapes. -/// -/// However, it is possible to keep a data source and customize these elements -/// based on the requirement. The value of the -/// [MapShapeLayerDelegate.shapeDataField] will be used to map with the -/// respective data returned in [MapShapeLayerDelegate.primaryValueMapper] -/// from the data source. -/// -/// Once the above mapping is done, you can customize the elements using the -/// APIs like [MapShapeLayerDelegate.dataLabelMapper], -/// [MapShapeLayerDelegate.shapeColorMappers], -/// [MapShapeLayerDelegate.shapeTooltipTextMapper], etc. -/// -/// ## Example -/// -/// This snippet shows how to create the [SfMaps]. -/// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return Container( -/// child: const SfMaps( -/// layers: [ -/// const MapShapeLayer( -/// delegate: const MapShapeLayerDelegate( -/// shapeFile: "assets/world_map.json", -/// shapeDataField: "continent"), -/// ) -/// ], -/// )); -/// } -/// ``` -/// -/// ## Tile layer -/// -/// Tile layer which renders the tiles returned from the Web Map Tile -/// Services (WMTS) like OpenStreetMap, Bing Maps, Google Maps, TomTom etc. -/// -/// The [MapTileLayer.urlTemplate] accepts the URL in WMTS format -/// i.e. {z} — zoom level, {x} and {y} — tile coordinates. -/// -/// This URL might vary slightly depends on the providers. The formats can be, -/// https://exampleprovider/{z}/{x}/{y}.png, -/// https://exampleprovider/z={z}/x={x}/y={y}.png, -/// https://exampleprovider/z={z}/x={x}/y={y}.png?key=subscription_key, etc. -/// -/// We will replace the {z}, {x}, {y} internally based on the -/// current center point and the zoom level. -/// -/// The subscription key may be needed for some of the providers. Please include -/// them in the [MapTileLayer.urlTemplate] itself as mentioned in above example. -/// Please note that the format may vary between the each map providers. -/// You can check the exact URL format needed for the providers in their -/// official websites. -/// -/// Regarding the tile rendering, at the lowest zoom level (Level 0), the map is -/// 256 x 256 pixels and the -/// whole world map renders as a single tile. At each increase in level, the map -/// width and height grow by a factor of 2 i.e. Level 1 is 512 x 512 pixels with -/// 4 tiles ((0, 0), (0, 1), (1, 0), (1, 1) where 0 and 1 are {x} and {y} in -/// [MapTileLayer.urlTemplate]), Level 2 is 2048 x 2048 pixels with 8 -/// tiles (from (0, 0) to (3, 3)), and so on. -/// (These details are just for your information and all these calculation are -/// done internally.) -/// -/// However, based on the size of the [SfMaps] widget, -/// [MapTileLayer.initialFocalLatLng] and [MapTileLayer.initialZoomLevel] number -/// of initial tiles needed in the view port alone will be rendered. -/// While zooming and panning, new tiles will be requested and rendered on -/// demand based on the current zoom level and focal point. -/// The current zoom level and focal point can be obtained from the -/// [MapZoomPanBehavior.zoomLevel] and [MapZoomPanBehavior.focalLatLng] -/// respectively. Once the particular tile is rendered, it will be stored in the -/// cache and it will be used from it next time for better performance. -/// -/// Regarding the cache and clearing it, please check the APIs in [imageCache] -/// (https://api.flutter.dev/flutter/painting/imageCache.html). -/// -/// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return SfMaps( -/// layers: [ -/// MapTileLayer( -/// urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', -/// initialFocalLatLng: MapLatLng(-23.698042, 133.880753), -/// initialZoomLevel: 3 -/// ), -/// ], -/// ); -/// } -/// ``` -/// See also: -/// -/// * [MapShapeLayer], for enabling the features like data labels, tooltip, -/// bubbles, legends, selection etc. -/// * [MapShapeLayerDelegate], for providing the data for the elements like data -/// labels, tooltip, bubbles, legends etc. -/// * For enabling zooming and panning, set [MapTileLayer.zoomPanBehavior] -/// with the instance of [MapZoomPanBehavior]. - -class SfMaps extends StatefulWidget { - /// Creates a [SfMaps]. - const SfMaps({ - Key key, - this.title, - this.layers, - }) : super(key: key); - - /// Title for the [SfMaps]. - /// - /// It can define and customize the title for the [SfMaps]. The text - /// property of the [MapTitle] is used to set the text of the title. - /// - /// It also provides option for customizing text style, alignment, decoration, - /// background color, margin and padding of the title. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// title: MapTitle( - /// text : 'World map' - /// ), - /// ); - /// } - /// ``` - final MapTitle title; - - /// The collection of map shape layer in which geographical rendering is done. - /// - /// The snippet below shows how to render the basic world map using the data - /// from .json file. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// delegate: MapShapeLayerDelegate( - /// shapeFile: "assets/world_map.json", - /// shapeDataField: "name", - /// ), - /// ) - /// ], - /// ); - /// } - /// ``` - /// See also: - /// * [MapShapeLayer.delegate], to provide data for the elements of the - /// [SfMaps] like data labels, bubbles, tooltip, shape colors, and legend. - final List layers; - - @override - _SfMapsState createState() => _SfMapsState(); -} - -class _SfMapsState extends State { - @override - Widget build(BuildContext context) { - assert(widget.layers != null && widget.layers.isNotEmpty); - return _MapsRenderObjectWidget( - child: widget.title != null && widget.title.text != null - ? Column( - children: [ - _title(context), - Expanded( - child: LayoutBuilder(builder: - (BuildContext context, BoxConstraints constraints) { - return Stack( - alignment: Alignment.center, - children: [widget.layers.last], - ); - }), - ), - ], - ) - : Stack( - alignment: Alignment.center, - children: [widget.layers.last], - ), - ); - } - - /// Returns the title of the [SfMaps]. - Widget _title(BuildContext context) { - final SfMapsThemeData themeData = SfMapsTheme.of(context); - return Align( - alignment: widget.title.alignment ?? Alignment.center, - child: Container( - child: Text( - widget.title.text, - style: widget.title.textStyle ?? - themeData.titleTextStyle ?? - Theme.of(context).textTheme.subtitle1, - ), - color: widget.title.color, - decoration: widget.title.decoration, - margin: widget.title.margin, - padding: - widget.title.padding ?? const EdgeInsets.symmetric(vertical: 8), - ), - ); - } -} - -class _MapsRenderObjectWidget extends SingleChildRenderObjectWidget { - const _MapsRenderObjectWidget({Key key, Widget child}) - : super(key: key, child: child); - - @override - _RenderMaps createRenderObject(BuildContext context) { - return _RenderMaps(); - } -} - -class _RenderMaps extends RenderProxyBox { - @override - void performLayout() { - final double width = - constraints.hasBoundedWidth ? constraints.maxWidth : 300; - final double height = - constraints.hasBoundedHeight ? constraints.maxHeight : 300; - child.layout(BoxConstraints.loose(Size(width, height)), - parentUsesSize: true); - size = child.size; - } -} diff --git a/packages/syncfusion_flutter_maps/lib/src/settings.dart b/packages/syncfusion_flutter_maps/lib/src/settings.dart new file mode 100644 index 000000000..4704ff9ba --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/settings.dart @@ -0,0 +1,1357 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import 'behavior/zoom_pan_behavior.dart'; +import 'elements/marker.dart'; +import 'enum.dart'; + +/// Signature used by the [MapShapeLayer.loadingBuilder]. +typedef MapLoadingBuilder = Widget Function(BuildContext context); + +/// Signature to return the string values from the data source +/// based on the index. +typedef IndexedStringValueMapper = String Function(int index); + +/// Signature to return the double values from the data source +/// based on the index. +typedef IndexedDoubleValueMapper = double Function(int index); + +/// Signature to return the colors or other types from the data source based on +/// the index based on which colors will be applied. +typedef IndexedColorValueMapper = dynamic Function(int index); + +/// Signature to return the [MapMarker]. +typedef MapMarkerBuilder = MapMarker Function(BuildContext context, int index); + +/// Signature for a [MapLayer.onWillZoom] callback which returns true or false +/// based on which current zooming completes. +typedef WillZoomCallback = bool Function(MapZoomDetails); + +/// Signature for a [MapLayer.onWillPan] callback which returns true or false +/// based on which current panning completes. +typedef WillPanCallback = bool Function(MapPanDetails); + +/// Customizes the shape or bubble color based on the data source and sets the +/// text and icon color for legend items. +/// +/// [MapShapeSource.shapeColorMappers] and +/// [MapShapeSource.bubbleColorMappers] accepts collection of +/// [MapColorMapper]. +/// +/// [MapShapeSource.shapeColorValueMapper] and +/// [MapShapeSource.bubbleColorValueMapper] returns a color or value +/// based on which shape or bubble color will be updated. +/// +/// If they return a color, then this color will be applied to the shapes or +/// bubbles straightaway. +/// +/// If they return a value other than the color, then you must set the +/// [MapShapeSource.shapeColorMappers] or +/// [MapShapeSource.bubbleColorMappers] property. +/// +/// The value returned from the [MapShapeSource.shapeColorValueMapper] +/// and [MapShapeSource.bubbleColorValueMapper] will be used for the +/// comparison in the [MapColorMapper.value] or [MapColorMapper.from] and +/// [MapColorMapper.to]. Then, the [MapColorMapper.color] will be applied to +/// the respective shape or bubble. +/// +/// Legend icon's color and text will be taken from [MapColorMapper.color] or +/// [MapColorMapper.text] respectively. +/// +/// The below code snippet represents how color can be applied to the shape +/// based on the [MapColorMapper.value] property of [MapColorMapper]. +/// +/// ```dart +/// List data; +/// +/// @override +/// void initState() { +/// super.initState(); +/// +/// data = [ +/// Model('India', 280, "Low"), +/// Model('United States of America', 190, "High"), +/// Model('Pakistan', 37, "Low"), +/// ]; +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// dataCount: data.length, +/// primaryValueMapper: (index) { +/// return data[index].country; +/// }, +/// shapeColorValueMapper: (index) { +/// return data[index].storage; +/// }, +/// shapeColorMappers: [ +/// MapColorMapper(value: "Low", color: Colors.red), +/// MapColorMapper(value: "High", color: Colors.green) +/// ]), +/// ) +/// ], +/// ); +/// } +/// ``` +/// The below code snippet represents how color can be applied to the shape +/// based on the range between [MapColorMapper.from] and [MapColorMapper.to] +/// properties of [MapColorMapper]. +/// +/// ```dart +/// List data; +/// +/// @override +/// void initState() { +/// super.initState(); +/// +/// data = [ +/// Model('India', 100, "Low"), +/// Model('United States of America', 200, "High"), +/// Model('Pakistan', 75, "Low"), +/// ]; +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// dataCount: data.length, +/// primaryValueMapper: (index) { +/// return data[index].country; +/// }, +/// shapeColorValueMapper: (index) { +/// return data[index].count; +/// }, +/// shapeColorMappers: [ +/// MapColorMapper(from: 0, to: 100, color: Colors.red), +/// MapColorMapper(from: 101, to: 200, color: Colors.yellow) +/// ]), +/// ) +/// ], +/// ); +/// } +/// ``` +/// +/// See also: +/// * [MapShapeSource.shapeColorValueMapper] and +/// [MapShapeSource.shapeColorMappers], to customize the shape colors +/// based on the data. +/// * [MapShapeSource.bubbleColorValueMapper] and +/// [MapShapeSource.bubbleColorMappers], to customize the shape colors +/// based on the data. +class MapColorMapper { + /// Creates a [MapColorMapper]. + const MapColorMapper({ + this.from, + this.to, + this.value, + this.color, + 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); + + /// Sets the range start for the color mapping. + /// + /// The shape or bubble will render in the specified [color] if the value + /// returned in the [MapShapeSource.shapeColorValueMapper] or + /// [MapShapeSource.bubbleColorValueMapper] falls between the [from] + /// and [to] range. + /// + /// ```dart + /// List data; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// data = [ + /// Model('India', 100, "Low"), + /// Model('United States of America', 200, "High"), + /// Model('Pakistan', 75, "Low"), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: data.length, + /// primaryValueMapper: (index) { + /// return data[index].country; + /// }, + /// shapeColorValueMapper: (index) { + /// return data[index].count; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(from: 0, to: 100, color: Colors.red), + /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) + /// ]), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [to], to set the range end for the range color mapping. + /// * [value], to set the value for the equal color mapping. + /// * [MapShapeSource.shapeColorMappers], to set the shape colors + /// based on the specific value. + /// * [MapShapeSource.bubbleColorMappers], to set the bubble colors + /// based on the specific value. + final double from; + + /// Sets the range end for the color mapping. + /// + /// The shape or bubble will render in the specified [color] if the value + /// returned in the [MapShapeSource.shapeColorValueMapper] or + /// [MapShapeSource.bubbleColorValueMapper] falls between the [from] + /// and [to] range. + /// + /// ```dart + /// List data; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// data = [ + /// Model('India', 100, "Low"), + /// Model('United States of America', 200, "High"), + /// Model('Pakistan', 75, "Low"), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: data.length, + /// primaryValueMapper: (index) { + /// return data[index].country; + /// }, + /// shapeColorValueMapper: (index) { + /// return data[index].count; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(from: 0, to: 100, color: Colors.red), + /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) + /// ]), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [from], to set the range start for the range color mapping. + /// * [value], to set the value for the equal color mapping. + /// * [MapShapeSource.shapeColorMappers], to set the shape colors based + /// on the specific value. + /// * [MapShapeSource.bubbleColorMappers], to set the bubble colors + /// based on the specific value. + final double to; + + /// Sets the value for the equal color mapping. + /// + /// The shape or bubble will render in the specified [color] if the value + /// returned in the [MapShapeSource.shapeColorValueMapper] or + /// [MapShapeSource.bubbleColorValueMapper] is equal to this [value]. + /// + /// ```dart + /// List data; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: data.length, + /// primaryValueMapper: (index) { + /// return data[index].country; + /// }, + /// shapeColorValueMapper: (index) { + /// return data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) + /// ]), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [color], to set the color for the shape or bubble. + /// * [MapShapeSource.shapeColorMappers], to set the shape colors + /// based on the specific value. + /// * [MapShapeSource.bubbleColorMappers], to set the bubble colors + /// based on the specific value. + final String value; + + /// Specifies the color applies to the shape or bubble based on the value. + /// + /// ```dart + /// List data; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: data.length, + /// primaryValueMapper: (index) { + /// return data[index].country; + /// }, + /// shapeColorValueMapper: (index) { + /// return data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) + /// ]), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [from], to set the range start for the range color mapping. + /// * [to], to set the range end for the range color mapping. + /// * [value], to set the value for the equal color mapping. + /// * [MapShapeSource.shapeColorMappers], to set the shape colors + /// based on the specific value. + /// * [MapShapeSource.bubbleColorMappers], to set the bubble colors + /// based on the specific value. + final Color color; + + /// Specifies the minimum opacity applies to the shape or bubble while using + /// [from] and [to]. + /// + /// The shapes or bubbles with lowest value which is [from] will be applied a + /// [minOpacity] and the shapes or bubbles with highest value which is [to] + /// will be applied a [maxOpacity]. The shapes or bubbles with values in- + /// between the range will get a opacity based on their respective value. + /// + /// ```dart + /// List data; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// data = [ + /// Model('India', 100, "Low"), + /// Model('United States of America', 200, "High"), + /// Model('Pakistan', 75, "Low"), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: data.length, + /// primaryValueMapper: (index) { + /// return data[index].country; + /// }, + /// shapeColorValueMapper: (index) { + /// return data[index].count; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(from: 0, to: 100, color: Colors.yellow, + /// maxOpacity: 0.2, minOpacity: 0.5), + /// MapColorMapper(from: 101, to: 200, color: Colors.red, + /// maxOpacity: 0.6, minOpacity: 1) + /// ]), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [MapShapeSource.shapeColorMappers], to set the shape colors + /// based on the specific value. + /// * [MapShapeSource.bubbleColorMappers], to set the bubble colors + /// based on the specific value. + final double minOpacity; + + /// Specifies the maximum opacity applies to the shape or bubble while using + /// [from] and [to]. + /// + /// The shapes or bubbles with lowest value which is [from] will be applied a + /// [minOpacity] and the shapes or bubbles with highest value which is [to] + /// will be applied a [maxOpacity]. The shapes or bubbles with values in- + /// between the range will get a opacity based on their respective value. + /// + /// ```dart + /// List data; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// data = [ + /// Model('India', 100, "Low"), + /// Model('United States of America', 200, "High"), + /// Model('Pakistan', 75, "Low"), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: data.length, + /// primaryValueMapper: (index) { + /// return data[index].country; + /// }, + /// shapeColorValueMapper: (index) { + /// return data[index].count; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(from: 0, to: 100, color: Colors.yellow, + /// maxOpacity: 0.2, minOpacity: 0.5), + /// MapColorMapper(from: 101, to: 200, color: Colors.red, + /// maxOpacity: 0.6, minOpacity: 1) + /// ]), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [MapShapeSource.shapeColorMappers], to set the shape colors based + /// on the specific value. + /// * [MapShapeSource.bubbleColorMappers], to set the bubble colors + /// based on the specific value. + final double maxOpacity; + + /// Specifies the text to be used for the legend item. + /// + /// By default, [MapColorMapper.from] and [MapColorMapper.to] or + /// [MapColorMapper.value] will be used as the text of the legend item. + /// + /// ```dart + /// List data; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// data = [ + /// Model('India', 100, "Low"), + /// Model('United States of America', 200, "High"), + /// Model('Pakistan', 75, "Low"), + /// ]; + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: data.length, + /// primaryValueMapper: (index) { + /// return data[index].country; + /// }, + /// shapeColorValueMapper: (index) { + /// return data[index].count; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(from: 0, to: 100, color: Colors.yellow, + /// maxOpacity: 0.2, minOpacity: 0.5, text: "low"), + /// MapColorMapper(from: 101, to: 200, color: Colors.red, + /// maxOpacity: 0.6, minOpacity: 1, text: "high") + /// ]), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [MapShapeSource.shapeColorMappers], to set the shape colors based + /// on the specific value. + /// * [MapShapeSource.bubbleColorMappers], to set the bubble colors + /// based on the specific value. + + final String text; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is MapColorMapper && + other.from == from && + other.to == to && + other.value == value && + other.color == color && + other.minOpacity == minOpacity && + other.maxOpacity == maxOpacity && + other.text == text; + } + + @override + int get hashCode => + hashValues(from, to, value, color, minOpacity, maxOpacity, text); +} + +/// Customizes the appearance of the data labels. +/// +/// It is possible to customize the style of the data labels, hide or trim the +/// data labels when it exceeds their respective shapes. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return +/// SfMaps( +/// layers: [ +/// MapShapeLayer( +/// dataLabelSettings: +/// MapDataLabelSettings( +/// textStyle: TextStyle(color: Colors.red) +/// ), +/// source: MapShapeSource.asset( +/// showDataLabels: true, +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// dataCount: bubbleData.length, +/// primaryValueMapper: (index) { +/// return bubbleData[index].country; +/// }), +/// ) +/// ], +/// ); +/// } +/// ``` +@immutable +class MapDataLabelSettings extends DiagnosticableTree { + /// Creates a [MapDataLabelSettings]. + const MapDataLabelSettings({ + this.textStyle, + this.overflowMode = MapLabelOverflow.visible, + }); + + /// Customizes the data label's text style. + /// + /// This snippet shows how to set [textStyle] for the data labels in [SfMaps]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// dataLabelSettings: + /// MapDataLabelSettings( + /// textStyle: TextStyle(color: Colors.red) + /// ), + /// source: MapShapeSource.asset( + /// showDataLabels: true, + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + final TextStyle textStyle; + + /// Trims or removes the data label when it is overflowed from the shape. + /// + /// Defaults to [MapLabelOverflow.visible]. + /// + /// By default, the data labels will render even if it overflows form the + /// shape. Using this property, it is possible to remove or trim the data + /// labels based on the available space in the shape. + /// + /// This snippet shows how to set the [overflowMode] for the data labels in + /// [SfMaps]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// dataLabelSettings: + /// MapDataLabelSettings( + /// overflowMode: MapLabelOverflow.hide + /// ), + /// source: MapShapeSource.asset( + /// showDataLabels: true, + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + final MapLabelOverflow overflowMode; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is MapDataLabelSettings && + other.textStyle == textStyle && + other.overflowMode == overflowMode; + } + + @override + int get hashCode => hashValues(textStyle, overflowMode); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + + if (textStyle != null) { + properties.add(textStyle.toDiagnosticsNode(name: 'textStyle')); + } + properties + .add(EnumProperty('overflowMode', overflowMode)); + } +} + +/// Customizes the appearance of the bubbles. +/// +/// It is possible to customize the radius, color, opacity and stroke of the +/// bubbles. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return +/// SfMaps( +/// layers: [ +/// MapShapeLayer( +/// bubbleSettings: MapBubbleSettings(maxRadius: 10, minRadius: 2), +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// dataCount: bubbleData.length, +/// primaryValueMapper: (index) { +/// return bubbleData[index].country; +/// }, +/// bubbleSizeMapper: (index) { +/// return bubbleData[index].usersCount; +/// }), +/// ) +/// ], +/// ); +/// } +/// ``` +@immutable +class MapBubbleSettings extends DiagnosticableTree { + /// Creates a [MapBubbleSettings]. + const MapBubbleSettings({ + this.minRadius = 10.0, + this.maxRadius = 50.0, + this.color, + this.strokeWidth, + this.strokeColor, + }); + + /// Minimum radius of the bubble. + /// + /// The radius of the bubble depends on the value returned in the + /// [MapShapeSource.bubbleSizeMapper]. From all the returned values, + /// the lowest value will have [minRadius] and the highest value will have + /// [maxRadius]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// bubbleSettings: MapBubbleSettings(maxRadius: 10, minRadius: 2), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }, + /// bubbleSizeMapper: (index) { + /// return bubbleData[index].usersCount; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + final double minRadius; + + /// Maximum radius of the bubble. + /// + /// The radius of the bubble depends on the value returned in the + /// [MapShapeSource.bubbleSizeMapper]. From all the returned values, + /// the lowest value will have [minRadius] and the highest value will have + /// [maxRadius]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// bubbleSettings: MapBubbleSettings(maxRadius: 10, minRadius: 2), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }, + /// bubbleSizeMapper: (index) { + /// return bubbleData[index].usersCount; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + final double maxRadius; + + /// Default color of the bubbles. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// bubbleSettings: MapBubbleSettings( + /// color: Colors.black), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }, + /// bubbleSizeMapper: (index) { + /// return bubbleData[index].usersCount; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [MapShapeSource.bubbleColorMappers] and + /// [MapShapeSource.bubbleColorValueMapper], to customize the bubble + /// colors based on the data. + final Color color; + + /// Stroke width of the bubbles. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// bubbleSettings: MapBubbleSettings( + /// strokeColor: Colors.red, + /// strokeWidth: 2), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }, + /// bubbleSizeMapper: (index) { + /// return bubbleData[index].usersCount; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [strokeColor], to set the stroke color. + final double strokeWidth; + + /// Stroke color of the bubbles. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// bubbleSettings: MapBubbleSettings( + /// strokeColor: Colors.red, + /// strokeWidth: 2), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }, + /// bubbleSizeMapper: (index) { + /// return bubbleData[index].usersCount; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [strokeWidth], to set the stroke width. + final Color strokeColor; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is MapBubbleSettings && + other.color == color && + other.strokeWidth == strokeWidth && + other.strokeColor == strokeColor && + other.minRadius == minRadius && + other.maxRadius == maxRadius; + } + + @override + int get hashCode => + hashValues(color, strokeWidth, strokeColor, maxRadius, minRadius); + + /// Creates a copy of this class but with the given fields + /// replaced with the new values. + MapBubbleSettings copyWith({ + double minRadius, + double maxRadius, + Color color, + double strokeWidth, + Color strokeColor, + }) { + return MapBubbleSettings( + minRadius: minRadius ?? this.minRadius, + maxRadius: maxRadius ?? this.maxRadius, + color: color ?? this.color, + strokeWidth: strokeWidth ?? this.strokeWidth, + strokeColor: strokeColor ?? this.strokeColor, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + if (strokeWidth != null) { + properties.add(DoubleProperty('strokeWidth', strokeWidth)); + } + + if (strokeColor != null) { + properties.add(ColorProperty('strokeColor', strokeColor)); + } + + properties.add(DoubleProperty('minRadius', minRadius)); + properties.add(DoubleProperty('maxRadius', maxRadius)); + } +} + +/// Customizes the appearance of the selected shape. +/// +/// ```dart +/// int _selectedIndex = -1; +/// +/// @override +/// Widget build(BuildContext context) { +/// return SfMaps( +/// layers: [ +/// MapShapeLayer( +/// selectedIndex: _selectedIndex, +/// onSelectionChanged: (int index) { +/// setState(() { +/// // Passing -1 to the unselect the previously selected shape. +/// _selectedIndex = (_selectedIndex == index) ? -1 : index; +/// }); +/// }, +/// selectionSettings: MapSelectionSettings( +/// color: Colors.black +/// ), +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// dataCount: bubbleData.length, +/// primaryValueMapper: (index) { +/// return bubbleData[index].country; +/// }), +/// ) +/// ], +/// ); +/// } +/// ``` +@immutable +class MapSelectionSettings extends DiagnosticableTree { + /// Creates a [MapSelectionSettings]. + const MapSelectionSettings({this.color, this.strokeColor, this.strokeWidth}); + + /// Fills the selected shape by this color. + /// + /// This snippet shows how to set selection color in [SfMaps]. + /// + /// ```dart + /// int _selectedIndex = -1; + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// selectedIndex: _selectedIndex, + /// onSelectionChanged: (int index) { + /// setState(() { + /// // Passing -1 to the unselect the previously selected shape. + /// _selectedIndex = (_selectedIndex == index) ? -1 : index; + /// }); + /// }, + /// selectionSettings: MapSelectionSettings( + /// color: Colors.black + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// See also: + /// * [strokeColor], to set stroke color for selected shape. + final Color color; + + /// Applies stroke color for the selected shape. + /// + /// This snippet shows how to set stroke color for the selected shape. + /// + /// ```dart + /// int _selectedIndex = -1; + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// selectedIndex: _selectedIndex, + /// onSelectionChanged: (int index) { + /// setState(() { + /// // Passing -1 to the unselect the previously selected shape. + /// _selectedIndex = (_selectedIndex == index) ? -1 : index; + /// }); + /// }, + /// selectionSettings: MapSelectionSettings( + /// strokeColor: Colors.white + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// See also: + /// * [Color], to set selected shape color. + final Color strokeColor; + + /// Stroke width which applies to the selected shape. + /// + /// This snippet shows how to set stroke width for the selected shape. + /// + /// ```dart + /// int _selectedIndex = -1; + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// selectedIndex: _selectedIndex, + /// onSelectionChanged: (int index) { + /// setState(() { + /// // Passing -1 to the unselect the previously selected shape. + /// _selectedIndex = (_selectedIndex == index) ? -1 : index; + /// }); + /// }, + /// selectionSettings: MapSelectionSettings( + /// strokeWidth: 2 + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + final double strokeWidth; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is MapSelectionSettings && + other.color == color && + other.strokeWidth == strokeWidth && + other.strokeColor == strokeColor; + } + + @override + int get hashCode => hashValues(color, strokeWidth, strokeColor); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + if (strokeWidth != null) { + properties.add(DoubleProperty('strokeWidth', strokeWidth)); + } + + if (strokeColor != null) { + properties.add(ColorProperty('strokeColor', strokeColor)); + } + } +} + +/// Customizes the appearance of the bubble's or shape's tooltip. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfMaps( +/// layers: [ +/// MapShapeLayer( +/// tooltipSettings: MapTooltipSettings( +/// color: Colors.black +/// ), +/// source: MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// dataCount: bubbleData.length, +/// primaryValueMapper: (index) { +/// return bubbleData[index].country; +/// }), +/// ) +/// ], +/// ); +/// } +/// ``` +/// +/// See also: +/// * [MapShapeLayer.shapeTooltipBuilder], for showing the completely +/// customized widget inside the tooltip. +@immutable +class MapTooltipSettings extends DiagnosticableTree { + /// Creates a [MapTooltipSettings]. + const MapTooltipSettings({ + this.color, + this.strokeWidth, + this.strokeColor, + }); + + /// Fills the tooltip by this color. + /// + /// This snippet shows how to set the tooltip color in [SfMaps]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// tooltipSettings: MapTooltipSettings( + /// color: Colors.black + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// See also: + /// * [textStyle], for customizing the style of the tooltip text. + final Color color; + + /// Specifies the stroke width applies to the tooltip. + /// + /// This snippet shows how to customize the stroke width in [SfMaps]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// tooltipSettings: MapTooltipSettings( + /// strokeWidth: 2 + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// See also: + /// * [strokeColor], for customizing the stroke color of the tooltip. + final double strokeWidth; + + /// Specifies the stroke color applies to the tooltip. + /// + /// This snippet shows how to customize stroke color in [SfMaps]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// tooltipSettings: MapTooltipSettings( + /// strokeColor: Colors.white + /// ), + /// source: MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: bubbleData.length, + /// primaryValueMapper: (index) { + /// return bubbleData[index].country; + /// }), + /// ) + /// ], + /// ); + /// } + /// ``` + /// + /// See also: + /// * [strokeWidth] for customizing the stroke width of the tooltip. + final Color strokeColor; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is MapTooltipSettings && + other.color == color && + other.strokeWidth == strokeWidth && + other.strokeColor == strokeColor; + } + + @override + int get hashCode => hashValues(color, strokeWidth, strokeColor); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + if (strokeWidth != null) { + properties.add(DoubleProperty('strokeWidth', strokeWidth)); + } + + if (strokeColor != null) { + properties.add(ColorProperty('strokeColor', strokeColor)); + } + } +} + +/// Provides options for customizing the appearance of the toolbar in the web +/// platform. +class MapToolbarSettings extends DiagnosticableTree { + /// Creates a [MapToolbarSettings]. + const MapToolbarSettings({ + this.iconColor, + this.itemBackgroundColor, + this.itemHoverColor, + this.direction = Axis.horizontal, + this.position = MapToolbarPosition.topRight, + }); + + /// Specifies the color applies to the tooltip icons. + final Color iconColor; + + /// Specifies the color applies to the tooltip icon's background. + final Color itemBackgroundColor; + + /// Specifies the color applies to the tooltip icon's background on hovering. + final Color itemHoverColor; + + /// Arranges the toolbar items in either horizontal or vertical direction. + /// + /// Defaults to [Axis.horizontal]. + final Axis direction; + + /// Option to position the toolbar in all the corners of the maps. + /// + /// Defaults to [MapToolbarPosition.topRight]. + final MapToolbarPosition position; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + + if (iconColor != null) { + properties.add(ColorProperty('iconColor', iconColor)); + } + if (itemBackgroundColor != null) { + properties.add(ColorProperty('itemBackgroundColor', itemBackgroundColor)); + } + + if (itemHoverColor != null) { + properties.add(ColorProperty('itemHoverColor', itemHoverColor)); + } + properties.add(EnumProperty('direction', direction)); + properties.add(EnumProperty('position', position)); + } +} diff --git a/packages/syncfusion_flutter_maps/lib/src/utils.dart b/packages/syncfusion_flutter_maps/lib/src/utils.dart new file mode 100644 index 000000000..8d8d0841b --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/utils.dart @@ -0,0 +1,129 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import 'behavior/zoom_pan_behavior.dart'; + +// ignore_for_file: public_member_api_docs + +// Default hover color opacity value. +const double hoverColorOpacity = 0.7; + +// If shape color opacity is same hover color opacity, +// hover is not visible in UI. +// So need to decrease hover opacity +const double minHoverOpacity = 0.5; + +// Using this factor, the tooltip position of the bubble and marker is +// determined from the total height. +const double tooltipHeightFactor = 0.25; + +const double minimumLatitude = -85.05112878; +const double maximumLatitude = 85.05112878; +const double minimumLongitude = -180; +const double maximumLongitude = 180; + +enum MarkerAction { insert, removeAt, replace, clear } + +Size getBoxSize(BoxConstraints constraints) { + final double width = constraints.hasBoundedWidth ? constraints.maxWidth : 300; + final double height = + constraints.hasBoundedHeight ? constraints.maxHeight : 300; + return Size(width, height); +} + +Offset pixelFromLatLng(num latitude, num longitude, Size size, + [Offset offset = const Offset(0, 0), double factor = 1.0]) { + assert(latitude != null); + assert(longitude != null); + assert(size != null); + + final double x = (longitude + 180.0) / 360.0; + final double sinLatitude = sin(latitude * pi / 180.0); + final double y = + 0.5 - log((1.0 + sinLatitude) / (1.0 - sinLatitude)) / (4.0 * pi); + final double mapSize = size.longestSide * factor; + final double dx = offset.dx + _clip(x * mapSize + 0.5, 0.0, mapSize - 1); + final double dy = offset.dy + _clip(y * mapSize + 0.5, 0.0, mapSize - 1); + return Offset(dx, dy); +} + +MapLatLng getPixelToLatLng( + Offset offset, Size size, Offset translation, double factor) { + return pixelToLatLng(offset, size, translation, factor); +} + +MapLatLng pixelToLatLng( + Offset offset, Size size, Offset translation, double factor) { + final double mapSize = size.longestSide * factor; + final double x = + (_clip(offset.dx - translation.dx, 0, mapSize - 1) / mapSize) - 0.5; + final double y = + 0.5 - (_clip(offset.dy - translation.dy, 0, mapSize - 1) / mapSize); + final double latitude = 90 - 360 * atan(exp(-y * 2 * pi)) / pi; + final double longitude = 360 * x; + return MapLatLng(latitude, longitude); +} + +double _clip(double value, double minValue, double maxValue) { + return min(max(value, minValue), maxValue); +} + +double interpolateValue(double value, double min, double max) { + assert(min != null); + max ??= value; + if (value > max) { + value = max; + } else if (value < min) { + value = min; + } + return value; +} + +String getTrimText(String text, TextStyle style, double maxWidth, + TextPainter painter, double width, + [double nextTextHalfWidth]) { + final int actualTextLength = text.length; + String trimmedText = text; + int trimLength = 3; // 3 dots + while (width > maxWidth) { + if (trimmedText.length <= 4) { + trimmedText = trimmedText[0] + '...'; + painter.text = TextSpan(style: style, text: trimmedText); + painter.layout(); + break; + } else { + trimmedText = text.replaceRange( + actualTextLength - trimLength, actualTextLength, '...'); + painter.text = TextSpan(style: style, text: trimmedText); + painter.layout(); + trimLength++; + } + + width = nextTextHalfWidth != null + ? painter.width / 2 + nextTextHalfWidth + : painter.width; + } + + return trimmedText; +} + +double getTotalTileWidth(double zoom) { + return 256 * pow(2, zoom); +} + +/// An interpolation between two latlngs. +/// +/// This class specializes the interpolation of [Tween] to use +/// [MapLatLng.lerp]. +class MapLatLngTween extends Tween { + /// Creates an [MapLatLng] tween. + MapLatLngTween({MapLatLng begin, MapLatLng end}) + : super(begin: begin, end: end); + + @override + MapLatLng lerp(double t) => MapLatLng.lerp(begin, end, t); +} diff --git a/packages/syncfusion_flutter_pdf/CHANGELOG.md b/packages/syncfusion_flutter_pdf/CHANGELOG.md index 8348c8adc..7cf9bcf9e 100644 --- a/packages/syncfusion_flutter_pdf/CHANGELOG.md +++ b/packages/syncfusion_flutter_pdf/CHANGELOG.md @@ -1,7 +1,72 @@ -## [18.3.35-beta] - 10/01/2020 +## [Unreleased] + +**Breaking changes** + +* The method `extractTextWithLine` has been removed and add new method `extractTextLines` instead. + +**Features** + +* Support provided to encrypt or decrypt a PDF document. +* Support provided to create, read, and edit layers in a PDF document. +* Support provided to create PDF conformance document. +* Support provided to extract text with the layout. +* Support provided to draw an image with pagination. +* Support provided to add an attachment to the PDF document. +* Support provided to add document information in a PDF document. + +**Bugs** + +* The bookmark parsing issue has been resolved now. + +## [18.3.52-beta] - 12/01/2020 + +* The method `extractTextWithLine` from `PdfTextExtractor` has been deprecated and added new method called `extractTextLines` instead. + +## [18.3.51-beta] - 11/24/2020 + +**Bugs** + +* The typecasting issue has been resolved now. + +## [18.3.48-beta] - 11/11/2020 No changes. +## [18.3.47-beta] - 11/05/2020 + +No changes. + +## [18.3.44-beta] - 10/27/2020 + +No changes. + +## [18.3.42-beta] - 10/20/2020 + +No changes. + +## [18.3.40-beta] - 10/13/2020 + +No changes. + +## [18.3.38-beta] - 10/07/2020 + +No changes. + +## [18.3.35-beta] - 10/01/2020 + +**Features** + +* Support provided to parse the existing PDF document. +* Support provided to add or remove the PDF pages in an existing PDF document. +* Support provided to add the graphical content to the existing PDF document page. +* Provided the incremental update support for the existing PDF document. +* Support provided to create and load the annotations in a new or existing PDF document. +* Support provided to load the existing PDF document bookmarks with its destination. +* Support provided to extract the text in an existing PDF document along with its bounds. +* Support provided to find the text in an existing PDF document along with its bounds and page index. +* Support provided to flatten the supported annotations in an existing PDF document. +* Support provided to save the PDF document with a cross-reference stream. + ## [18.2.59-beta.1] - 09/24/2020 **Bugs** @@ -10,9 +75,7 @@ No changes. ## [18.2.59-beta] - 09/23/2020 -**Bugs** - -* Now, the issue with package version solving failed has been resolved. +No changes. ## [18.2.57-beta] - 09/08/2020 @@ -22,7 +85,7 @@ No changes. No changes. -## [18.2.55-beta] - 08/25/2020 +## [18.2.55-beta] - 08/25/2020 No changes. diff --git a/packages/syncfusion_flutter_pdf/README.md b/packages/syncfusion_flutter_pdf/README.md index 1e4f2ec5a..1d7f73f16 100644 --- a/packages/syncfusion_flutter_pdf/README.md +++ b/packages/syncfusion_flutter_pdf/README.md @@ -2,13 +2,13 @@ # Syncfusion Flutter PDF -Syncfusion Flutter PDF is a feature rich and high-performance non-UI PDF library written natively in Dart. It allows you to add robust PDF functionalities to Flutter applications. +Syncfusion Flutter PDF is a feature-rich and high-performance non-UI PDF library written natively in Dart. It allows you to add robust PDF functionalities to Flutter applications. ## Overview -The PDF package is a non-UI and reusable flutter library to create PDF reports programmatically with formatted text, images, shapes, tables, links, lists, header and footer, and more. The creation of PDF file follows the most popular PDF 1.7 (ISO 32000-1) and latest PDF 2.0 (ISO 32000-2) specifications. +The PDF package is a non-UI and reusable Flutter library for creating PDF reports programmatically with formatted text, images, shapes, tables, links, lists, headers, footers, and more. The library can be used in Flutter mobile and web platforms without dependency on Adobe Acrobat. The creation of a PDF follows the most popular PDF 1.7 (ISO 32000-1) and latest PDF 2.0 (ISO 32000-2) specifications. -**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion Commercial License or Syncfusion Community license. For more details, please check the [LICENSE](LICENSE) file. +**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or Syncfusion Community License. For more details, please check the [LICENSE](LICENSE) file. **Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. @@ -27,26 +27,35 @@ The PDF package is a non-UI and reusable flutter library to create PDF reports p - [Add bullets and lists](#add-bullets-and-lists) - [Add tables](#add-tables) - [Add headers and footers](#add-headers-and-footers) + - [Load and modify an existing PDF document](#load-and-modify-an-existing-pdf-document) + - [Create and load annotations](#create-and-load-annotations) + - [Add bookmarks](#add-bookmarks) + - [Extract text](#extract-text) + - [Find text](#find-text) + - [Encryption and decryption](#encryption-and-decryption) + - [PDF conformance](#pdf-conformance) - [Support and feedback](#support-and-feedback) - [About Syncfusion](#about-syncfusion) -## Key Features +## Key features -The following are the key features of Syncfusion Flutter PDF. +The following are the key features of Syncfusion Flutter PDF: * Create multipage PDF files from scratch. * Add Unicode and RTL text. * Insert JPEG and PNG images to the PDF document. * Generate table in PDF files with different styles and formats. -* Add headers and footers to the PDF file. +* Add headers and footers. * Add different shapes to PDF file. -* Add hyperlinks and bookmarks to the PDF file. -* Add paragraph, bullets, and lists to the PDF file. -* Mobile and web platforms support. +* Add, modify, and remove interactive elements such as bookmarks, hyperlinks and attachments. +* Add paragraph, bullets, and lists. +* Open, modify, and save existing PDF files. +* Ability to encrypt and decrypt PDF files with advanced standards. +* Use on mobile and web platforms. ## Get the demo application -Explore the full capability of our Flutter widgets on your device by installing our sample browser application from the following app stores, and view sample’ codes in GitHub. +Explore the full capability of our Flutter widgets on your device by installing our sample browser application from the following app stores and view sample code in GitHub.

@@ -61,12 +70,13 @@ Explore the full capability of our Flutter widgets on your device by installing Take a look at the following to learn more about Syncfusion Flutter PDF: +* [Syncfusion Flutter PDF product page](https://www.syncfusion.com/flutter-widgets/pdf-library) * [User guide documentation](https://help.syncfusion.com/flutter/pdf/overview) * [Knowledge base](https://www.syncfusion.com/kb) ## Installation -Install the latest version from [pub](https://pub.dartlang.org/packages/syncfusion_flutter_pdf#-installing-tab-). +Install the latest version from [pub.dev](https://pub.dartlang.org/packages/syncfusion_flutter_pdf#-installing-tab-). ## Getting started @@ -99,40 +109,40 @@ document.dispose(); Use the following code to add a Unicode text to the PDF document. ```dart -//Create a new PDF document +//Create a new PDF document. final PdfDocument document = PdfDocument(); -//Read font data +//Read font data. final Uint8List fontData = File('arial.ttf').readAsBytesSync(); -//Create a PDF true type font object +//Create a PDF true type font object. final PdfFont font = PdfTrueTypeFont(fontData, 12); -//Draw text using ttf font +//Draw text using ttf font. document.pages.add().graphics.drawString('Hello World!!!', font, bounds: const Rect.fromLTWH(0, 0, 200, 50)); -// Save the document +// Save the document. File('TrueType.pdf').writeAsBytes(document.save()); -// Dispose the document +// Dispose the document. document.dispose(); ``` ### Add images to a PDF document -PdfBitmap class is used to draw images to a PDF document. Now, Syncfusion Flutter PDF supports only PNG and JPEG images. Refer to the following code to draw images to a PDF document. +The PdfBitmap class is used to draw images in a PDF document. Syncfusion Flutter PDF supports PNG and JPEG images. Refer to the following code to draw images in a PDF document. ```dart -//Create a new PDF document +//Create a new PDF document. final PdfDocument document = PdfDocument(); -//Read image data +//Read image data. final Uint8List imageData = File('input.png').readAsBytesSync(); -//Load the image using PdfBitmap +//Load the image using PdfBitmap. final PdfBitmap image = PdfBitmap(imageData); -//Draw the image to the PDF page +//Draw the image to the PDF page. document.pages .add() .graphics .drawImage(image, const Rect.fromLTWH(0, 0, 500, 200)); -// Save the document +// Save the document. File('ImageToPDF.pdf').writeAsBytes(document.save()); -// Dispose the document +// Dispose the document. document.dispose(); ``` @@ -148,11 +158,11 @@ const String paragraphText = 'In addition, PDF supports user interaction and collaborative workflows that are not' 'possible with printed documents.'; -// Create a new PDF document +// Create a new PDF document. final PdfDocument document = PdfDocument(); -// Add a new page to the document +// Add a new page to the document. final PdfPage page = document.pages.add(); -// Create a new PDF text element class and draw the flow layout text +// Create a new PDF text element class and draw the flow layout text. final PdfLayoutResult layoutResult = PdfTextElement( text: paragraphText, font: PdfStandardFont(PdfFontFamily.helvetica, 12), @@ -162,14 +172,14 @@ final PdfLayoutResult layoutResult = PdfTextElement( bounds: Rect.fromLTWH( 0, 0, page.getClientSize().width, page.getClientSize().height), format: PdfLayoutFormat(layoutType: PdfLayoutType.paginate)); -// Draw the next paragraph/content +// Draw the next paragraph/content. page.graphics.drawLine( PdfPen(PdfColor(255, 0, 0)), Offset(0, layoutResult.bounds.bottom + 10), Offset(page.getClientSize().width, layoutResult.bounds.bottom + 10)); -// Save the document +// Save the document. File('TextFlow.pdf').writeAsBytes(document.save()); -// Dispose the document +// Dispose the document. document.dispose(); ``` @@ -178,11 +188,11 @@ document.dispose(); Add the following code to create bullets and lists in a PDF document. ```dart -// Create a new PDF document +// Create a new PDF document. final PdfDocument document = PdfDocument(); -// Add a new page to the document +// Add a new page to the document. final PdfPage page = document.pages.add(); -// Create a PDF ordered list +// Create a PDF ordered list. final PdfOrderedList orderedList = PdfOrderedList( items: PdfListItemCollection([ 'Mammals', @@ -211,14 +221,14 @@ orderedList.items[0].subList = PdfUnorderedList( ]), textIndent: 10, indent: 20); -// Draw the list to the PDF page +// Draw the list to the PDF page. orderedList.draw( page: page, bounds: Rect.fromLTWH( 0, 0, page.getClientSize().width, page.getClientSize().height)); -// Save the document +// Save the document. File('BulletandList.pdf').writeAsBytes(document.save()); -// Dispose the document +// Dispose the document. document.dispose(); ``` @@ -227,47 +237,47 @@ document.dispose(); Add the following code to create a PDF table. ```dart -// Create a new PDF document +// Create a new PDF document. final PdfDocument document = PdfDocument(); -// Add a new page to the document +// Add a new page to the document. final PdfPage page = document.pages.add(); -// Create a PDF grid class to add tables +// Create a PDF grid class to add tables. final PdfGrid grid = PdfGrid(); -// Specify the grid columns count +// Specify the grid columns count. grid.columns.add(count: 3); -// Add a grid header row +// Add a grid header row. final PdfGridRow headerRow = grid.headers.add(1)[0]; headerRow.cells[0].value = 'Customer ID'; headerRow.cells[1].value = 'Contact Name'; headerRow.cells[2].value = 'Country'; -// Set header font +// Set header font. headerRow.style.font = PdfStandardFont(PdfFontFamily.helvetica, 10, style: PdfFontStyle.bold); -// Add rows to the grid +// Add rows to the grid. PdfGridRow row = grid.rows.add(); row.cells[0].value = 'ALFKI'; row.cells[1].value = 'Maria Anders'; row.cells[2].value = 'Germany'; -// Add next row +// Add next row. row = grid.rows.add(); row.cells[0].value = 'ANATR'; row.cells[1].value = 'Ana Trujillo'; row.cells[2].value = 'Mexico'; -// Add next row +// Add next row. row = grid.rows.add(); row.cells[0].value = 'ANTON'; row.cells[1].value = 'Antonio Mereno'; row.cells[2].value = 'Mexico'; -// Set grid format +// Set grid format. grid.style.cellPadding = PdfPaddings(left: 5, top: 5); -// Draw table to the PDF page. +// Draw table in the PDF page. grid.draw( page: page, bounds: Rect.fromLTWH( 0, 0, page.getClientSize().width, page.getClientSize().height)); -// Save the document +// Save the document. File('PDFTable.pdf').writeAsBytes(document.save()); -// Dispose the document +// Dispose the document. document.dispose(); ``` @@ -276,45 +286,245 @@ document.dispose(); Use the following code to add headers and footers to a PDF document. ```dart -//Create a new PDF document +//Create a new PDF document. final PdfDocument document = PdfDocument(); -//Create a PDF page template and add a header content +//Create a PDF page template and add header content. final PdfPageTemplateElement headerTemplate = PdfPageTemplateElement(const Rect.fromLTWH(0, 0, 515, 50)); -//Draw text to the header +//Draw text in the header. headerTemplate.graphics.drawString( 'This is page header', PdfStandardFont(PdfFontFamily.helvetica, 12), bounds: const Rect.fromLTWH(0, 15, 200, 20)); -//Add the header element to the document +//Add the header element to the document. document.template.top = headerTemplate; -//Create a PDF page template and add a footer content +//Create a PDF page template and add footer content. final PdfPageTemplateElement footerTemplate = PdfPageTemplateElement(const Rect.fromLTWH(0, 0, 515, 50)); -//Draw text to the footer +//Draw text in the footer. footerTemplate.graphics.drawString( 'This is page footer', PdfStandardFont(PdfFontFamily.helvetica, 12), bounds: const Rect.fromLTWH(0, 15, 200, 20)); -//Set footer to the document. +//Set footer in the document. document.template.bottom = footerTemplate; -//Now create pages +//Now create pages. document.pages.add(); document.pages.add(); -// Save the document +// Save the document. File('HeaderandFooter.pdf').writeAsBytes(document.save()); -// Dispose the document +// Dispose the document. +document.dispose(); +``` + +### Load and modify an existing PDF document + +Add the following code to load and modify the existing PDF document. + +```dart +//Load the existing PDF document. +final PdfDocument document = + PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); +//Get the existing PDF page. +final PdfPage page = document.pages[0]; +//Draw text in the PDF page. +page.graphics.drawString( + 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + brush: PdfSolidBrush(PdfColor(0, 0, 0)), + bounds: const Rect.fromLTWH(0, 0, 150, 20)); +//Save the document. +File('output.pdf').writeAsBytes(document.save()); +//Dispose the document. +document.dispose(); +``` + +Add the following code to add or remove a page from the existing PDF document. + +```dart +//Load the existing PDF document. +final PdfDocument document = + PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); +//Remove the page from the document. +document.pages.removeAt(0); +//Add new page and draw text. +document.pages.add().graphics.drawString( + 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + brush: PdfSolidBrush(PdfColor(0, 0, 0)), + bounds: const Rect.fromLTWH(0, 0, 150, 20)); +//Save the document. +File('output.pdf').writeAsBytes(document.save()); +//Dispose the document. +document.dispose(); +``` + +### Create and load annotations + +Using this package, we can create and load annotations in a new or existing PDF document. + +Add the following code to create a new annotation in a PDF document. + +```dart +//Load the existing PDF document. +final PdfDocument document = + PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); +//Create a new rectangle annotation and add to the PDF page. +document.pages[0].annotations.add(PdfRectangleAnnotation( + Rect.fromLTWH(0, 0, 150, 100), 'Rectangle', + color: PdfColor(255, 0, 0), setAppearance: true)); +//Save the document. +File('output.pdf').writeAsBytes(document.save()); +//Dispose the document. +document.dispose(); +``` + +Add the following code to load the annotation and modify it. + +```dart +//Load and modify the existing annotation. +final PdfRectangleAnnotation rectangleAnnotation = + document.pages[0].annotations[0]; +//Change the annotation text. +rectangleAnnotation.text = 'Changed'; +``` + +Refer to our documentation for more details about [annotations](https://help.syncfusion.com/flutter/pdf/working-with-annotations). + +### Add bookmarks + +Add the following code to create bookmarks in a PDF document. + +```dart +//Load the existing PDF document. +final PdfDocument document = + PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); +//Create a document bookmark. +final PdfBookmark bookmark = document.bookmarks.add('Page 1'); +//Set the destination page and location. +bookmark.destination = PdfDestination(document.pages[1], Offset(20, 20)); +//Set the bookmark color. +bookmark.color = PdfColor(255, 0, 0); +//Save the document. +File('output1.pdf').writeAsBytes(document.save()); +//Dispose the document. +document.dispose(); +``` + +Refer to our documentation for more details about [bookmarks](https://help.syncfusion.com/flutter/pdf/working-with-bookmarks). + +### Extract text + +Using this package, we can extract text from an existing PDF document along with its bounds. + +Add the following code to extract text from a PDF document. + +```dart +//Load an existing PDF document. +final PdfDocument document = + PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); +//Extract the text from all the pages. +String text = PdfTextExtractor(document).extractText(); +//Dispose the document. +document.dispose(); +``` + +The following code sample explains how to extract text from a specific page. + +```dart +//Load an existing PDF document. +PdfDocument document = + PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); +//Extract the text from page 1. +String text = PdfTextExtractor(document).extractText(startPageIndex: 0); +//Dispose the document. +document.dispose(); +``` + +Refer to our [documentation](https://help.syncfusion.com/flutter/pdf/working-with-text-extraction) for more details. + +### Find text + +Using this package, we can find text in an existing PDF document along with its bounds and page index. + +Add the following code to find text in a PDF document. + +```dart +//Load an existing PDF document. +PdfDocument document = + PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); +//Find the text and get matched items. +List textCollection = + PdfTextExtractor(document).findText(['text1', 'text2']); +//Get the matched item in the collection using index. +MatchedItem matchedText = textCollection[0]; +//Get the text bounds. +Rect textBounds = matchedText.bounds; +//Get the page index. +int pageIndex = matchedText.pageIndex; +//Get the text. +String text = matchedText.text; +//Dispose the document. +document.dispose(); +``` + +Refer to our [documentation](https://help.syncfusion.com/flutter/pdf/working-with-text-extraction#working-with-find-text) for more details. + +## Encryption and decryption + +Encrypt new or existing PDF documents with encryption standards like 40-bit RC4, 128-bit RC4, 128-bit AES, 256-bit AES, and advanced encryption standard 256-bit AES Revision 6 (PDF 2.0) to protect documents against unauthorized access. Using this package, you can also decrypt existing encrypted documents. + +Add the following code to encrypt an existing PDF document. + +```dart +//Load the existing PDF document. +final PdfDocument document = + PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); + +//Add security to the document. +final PdfSecurity security = document.security; + +//Set password. +security.userPassword = 'userpassword@123'; +security.ownerPassword = 'ownerpassword@123'; + +//Set the encryption algorithm. +security.algorithm = PdfEncryptionAlgorithm.aesx256Bit; + +//Save the document. +File('output1.pdf').writeAsBytes(document.save()); + +//Dispose the document. +document.dispose(); +``` + +## PDF conformance + +Using this package, we can create PDF conformance documents, such as: + +* PDF/A-1B +* PDF/A-2B +* PDF/A-3B + +Add the following code to create a PDF conformance document. + +```dart +//Create a PDF conformance document. +final PdfDocument document = PdfDocument(conformanceLevel: PdfConformanceLevel.a1b) + ..pages.add().graphics.drawString('Hello World', + PdfTrueTypeFont(File('Roboto-Regular.ttf').readAsBytesSync(), 12), + bounds: Rect.fromLTWH(20, 20, 200, 50), brush: PdfBrushes.black); +//Save and dispose the document. +File('output.pdf').writeAsBytesSync(document.save()); document.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). -* 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. +* For any questions, please contact our [Syncfusion support team](https://www.syncfusion.com/support/directtrac/incidents/newincident) or post them in our [community forums](https://www.syncfusion.com/forums). You can also submit a feature request or a bug alert through our [feedback portal](https://www.syncfusion.com/feedback/flutter). +* 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 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 22,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. +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), and [UWP](https://www.syncfusion.com/uwp-ui-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. diff --git a/packages/syncfusion_flutter_pdf/lib/pdf.dart b/packages/syncfusion_flutter_pdf/lib/pdf.dart index 92ee38eb3..6ee6dfd1f 100644 --- a/packages/syncfusion_flutter_pdf/lib/pdf.dart +++ b/packages/syncfusion_flutter_pdf/lib/pdf.dart @@ -5,9 +5,11 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:math'; import 'dart:ui'; +import 'package:crypto/crypto.dart'; import 'package:flutter/material.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/intl.dart'; +import 'package:xml/xml.dart'; part 'src/pdf/implementation/pdf_document/pdf_document.dart'; part 'src/pdf/implementation/pdf_document/pdf_catalog.dart'; @@ -179,6 +181,18 @@ part 'src/pdf/implementation/exporting/pdf_text_extractor/matrix_helper.dart'; part 'src/pdf/implementation/exporting/pdf_text_extractor/glyph.dart'; part 'src/pdf/implementation/exporting/pdf_text_extractor/text_element.dart'; part 'src/pdf/implementation/exporting/pdf_text_extractor/graphic_object_data_collection.dart'; +part 'src/pdf/implementation/pdf_document/pdf_document_information.dart'; +part 'src/pdf/implementation/xmp/xmp_metadata.dart'; +part 'src/pdf/implementation/color_space/pdf_icc_color_profile.dart'; +part 'src/pdf/implementation/pages/pdf_layer_collection.dart'; +part 'src/pdf/implementation/pages/pdf_layer.dart'; +part 'src/pdf/implementation/pdf_document/pdf_catalog_names.dart'; +part 'src/pdf/implementation/pdf_document/attachments/pdf_attachment.dart'; +part 'src/pdf/implementation/pdf_document/attachments/pdf_attachment_collection.dart'; +part 'src/pdf/implementation/general/embedded_file.dart'; +part 'src/pdf/implementation/general/embedded_file_specification.dart'; +part 'src/pdf/implementation/general/file_specification_base.dart'; +part 'src/pdf/implementation/general/embedded_file_params.dart'; /// Compression part 'src/pdf/implementation/compression/compressed_stream_writer.dart'; @@ -193,3 +207,12 @@ part 'src/pdf/implementation/compression/pdf_zlib_compressor.dart'; part 'src/pdf/implementation/compression/compressed_stream_reader.dart'; part 'src/pdf/implementation/compression/decompressor_huffman_tree.dart'; part 'src/pdf/implementation/compression/pdf_png_filter.dart'; + +/// Security +part 'src/pdf/implementation/security/pdf_security.dart'; +part 'src/pdf/implementation/security/pdf_encryptor.dart'; +part 'src/pdf/implementation/security/enum.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/aes_engine.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/aes_cipher.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/cipher_block_chaining_mode.dart'; + 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 2949cf498..76fe6915e 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 @@ -430,6 +430,23 @@ abstract class PdfAnnotation implements _IPdfWrapper { } void _save() { + if (_page._document != null && + _page._document._conformanceLevel != PdfConformanceLevel.none) { + if (this is PdfActionAnnotation && + _page._document._conformanceLevel == PdfConformanceLevel.a1b) { + throw ArgumentError( + 'The specified annotation type is not supported by PDF/A1-B or PDF/A1-A standard documents.'); + } + //This is needed to attain specific PDF/A conformance. + if (!(this is PdfLinkAnnotation) && + !setAppearance && + (_page._document._conformanceLevel == PdfConformanceLevel.a2b || + _page._document._conformanceLevel == PdfConformanceLevel.a3b)) { + throw ArgumentError( + 'The appearance dictionary doesn\'t contain an entry. Enable setAppearance in PdfAnnotation class to overcome this error.'); + } + _dictionary._setNumber(_DictionaryProperties.f, 4); + } if (_border != null) { if (_isLineBorder()) { _dictionary.setProperty(_DictionaryProperties.bs, border); 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 b6a2b4b4a..a2acb8a57 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 @@ -42,18 +42,35 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { if (_dictionary.containsKey(_DictionaryProperties.dest)) { final _IPdfPrimitive obj = _crossTable._getObject(_dictionary[_DictionaryProperties.dest]); - final _PdfArray array = obj as _PdfArray; + _PdfArray array; + if (obj is _PdfArray) { + array = obj; + } else if (_crossTable._document != null && + _crossTable._document._isLoadedDocument) { + if (obj is _PdfName || obj is _PdfString) { + array = _crossTable._document._getNamedDestination(obj); + } + } PdfPage page; - if (array[0] is _PdfReferenceHolder) { + if (array != null && array[0] is _PdfReferenceHolder) { final _PdfDictionary dic = _crossTable ._getObject(array[0] as _PdfReferenceHolder) as _PdfDictionary; page = _crossTable._document.pages._getPage(dic); final _PdfName mode = array[1] as _PdfName; if (page != null && mode != null) { if (mode._name == 'XYZ') { - final _PdfNumber left = array[2] as _PdfNumber; - final _PdfNumber top = array[3] as _PdfNumber; - final _PdfNumber zoom = array[4] as _PdfNumber; + _PdfNumber left; + _PdfNumber top; + _PdfNumber zoom; + if (array[2] is _PdfNumber) { + left = array[2] as _PdfNumber; + } + if (array[3] is _PdfNumber) { + top = array[3] as _PdfNumber; + } + if (array[4] is _PdfNumber) { + zoom = array[4] as _PdfNumber; + } final double topValue = (top == null) ? 0 : page.size.height - (top.value.toDouble()); final double leftValue = (left == null) ? 0 : left.value.toDouble(); @@ -61,11 +78,15 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { if (zoom != null) { _destination.zoom = zoom.value.toDouble(); } + _destination.mode = PdfDestinationMode.location; } else if (mode._name == 'Fit') { _destination = PdfDestination(page); _destination.mode = PdfDestinationMode.fitToPage; } else if (mode._name == 'FitH') { - final _PdfNumber top = array[2] as _PdfNumber; + _PdfNumber top; + if (array[2] is _PdfNumber) { + top = array[2] as _PdfNumber; + } final double topValue = (page.size.height - top.value).toDouble(); _destination = PdfDestination(page, Offset(0, topValue)); _destination.mode = PdfDestinationMode.fitH; @@ -91,8 +112,16 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { if (obj is _PdfReferenceHolder) { obj = (obj as _PdfReferenceHolder).object; } - final _PdfArray array = obj as _PdfArray; - if (array != null) { + _PdfArray array; + if (obj is _PdfArray) { + array = obj; + } else if (_crossTable._document != null && + _crossTable._document._isLoadedDocument) { + if (obj is _PdfName || obj is _PdfString) { + array = _crossTable._document._getNamedDestination(obj); + } + } + if (array != null && array[0] is _PdfReferenceHolder) { final _PdfReferenceHolder holder = array[0] as _PdfReferenceHolder; PdfPage page; if (holder != null) { @@ -106,15 +135,27 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { if (page != null) { final _PdfName mode = array[1] as _PdfName; if (mode._name == 'FitBH' || mode._name == 'FitH') { - final _PdfNumber top = array[2] as _PdfNumber; + _PdfNumber top; + if (array[2] is _PdfNumber) { + top = array[2] as _PdfNumber; + } final double topValue = (top == null) ? 0 : page.size.height - (top.value.toDouble()); _destination = PdfDestination(page, Offset(0, topValue)); _destination.mode = PdfDestinationMode.fitH; } else if (mode._name == 'XYZ') { - final _PdfNumber left = array[2] as _PdfNumber; - final _PdfNumber top = array[3] as _PdfNumber; - final _PdfNumber zoom = array[4] as _PdfNumber; + _PdfNumber left; + _PdfNumber top; + _PdfNumber zoom; + if (array[2] is _PdfNumber) { + left = array[2] as _PdfNumber; + } + if (array[3] is _PdfNumber) { + top = array[3] as _PdfNumber; + } + if (array[4] is _PdfNumber) { + zoom = array[4] as _PdfNumber; + } if (page != null) { final double topValue = (top == null) ? 0 : page.size.height - (top.value.toDouble()); 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 new file mode 100644 index 000000000..2e0bf836c --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/color_space/pdf_icc_color_profile.dart @@ -0,0 +1,38 @@ +part of pdf; + +/// Class represents the ICC Colorspace. +//Used during creation of documents with PDF/A1B compliance. +class _PdfICCColorProfile implements _IPdfWrapper { + //Constructor + //Initializes a new instance of the [PdfICCColorProfile] class. + _PdfICCColorProfile() : super() { + _stream.compress = true; + _stream.setProperty(_DictionaryProperties.filter, + _PdfName(_DictionaryProperties.flateDecode)); + _stream.setProperty(_DictionaryProperties.n, _PdfNumber(3)); + _stream._beginSave = _beginSaveStream; + } + + //Fields + //Internal variable to store the stream.. + final _PdfStream _stream = _PdfStream(); + final String profileData = + 'AAAMSExpbm8CEAAAbW50clJHQiBYWVogB84AAgAJAAYAMQAAYWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1IUCAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARY3BydAAAAVAAAAAzZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtwdAAAAgQAAAAUclhZWgAAAhgAAAAUZ1hZWgAAAiwAAAAUYlhZWgAAAkAAAAAUZG1uZAAAAlQAAABwZG1kZAAAAsQAAACIdnVlZAAAA0wAAACGdmlldwAAA9QAAAAkbHVtaQAAA/gAAAAUbWVhcwAABAwAAAAkdGVjaAAABDAAAAAMclRSQwAABDwAAAgMZ1RSQwAABDwAAAgMYlRSQwAABDwAAAgMdGV4dAAAAABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1QYWNrYXJkIENvbXBhbnkAAGRlc2MAAAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAADzUQABAAAAARbMWFlaIAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9kZXNjAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2aWV3AAAAAAATpP4AFF8uABDPFAAD7cwABBMLAANcngAAAAFYWVogAAAAAABMCVYAUAAAAFcf521lYXMAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYAAAAAAAAEAAAAAAUACgAPABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwAgQCGAIsAkACVAJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4AkECSwJUAl0CZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oDZgNyA34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYExATTBOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkGagZ7BowGnQavBsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIIIlgiqCL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK3ArzCwsLIgs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUMjgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3eDfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8lD0EPXg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHoEgcSJhJFEmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVWFXgVmxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY+hkgGUUZaxmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzMHPUdHh1HHXAdmR3DHeweFh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFIIXUhoSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWXJccl9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1KmgqmyrPKwIrNitpK50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/HL/4wNTBsMKQw2zESMUoxgjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPARANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP3VAnUHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dEV5JX4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9fYV+zYAVgV2CqYPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+loP2iWaOxpQ2maafFqSGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpxlXHwcktypnMBc11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8gXzhfUF9oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZif6KZIrKizCLlov8jGOMyo0xjZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSWn5cKl3WX4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobaiJqKWowajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1ErbiuLa6hrxavi7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6tbsuu6e8IbybvRW9j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9yLzJOsm5yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/gNuC94UThzOJT4tvjY+Pr5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c7ijutO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8B/yY/Sn9uv5L/tz/bf//'; + + //Implementation. + //Handles the BeginSave event of the Stream control. + void _beginSaveStream(Object sender, _SavePdfPrimitiveArgs args) { + _stream._clearStream(); + _stream._dataStream = base64.decode(profileData).toList(); + } + + //Overrides. + // Gets the element. + @override + _IPdfPrimitive get _element => _stream; + + @override + set _element(_IPdfPrimitive value) { + throw ArgumentError('value of element cannot be set'); + } +} 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 15b1ca7a5..6167a1a57 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 @@ -44,6 +44,7 @@ class _FontStructure { } //Fields + bool isWhiteSpace = false; bool isSameFont = false; String _fontEncoding; _PdfDictionary fontDictionary; @@ -90,6 +91,7 @@ class _FontStructure { List standardFontNames; List standardCJKFontNames; List cjkEncoding; + List _windows1252MapTable; //Properties String get fontEncoding => _fontEncoding ??= getFontEncoding(); @@ -245,6 +247,263 @@ class _FontStructure { 'Symbol', 'ZapfDingbats' ]; + _windows1252MapTable = [ + '\0', + '\u0001', + '\u0002', + '\u0003', + '\u0004', + '\u0005', + '\u0006', + '\a', + '\b', + '\t', + '\n', + '\v', + '\f', + '\r', + '\u000e', + '\u000f', + '\u0010', + '\u0011', + '\u0012', + '\u0013', + '\u0014', + '\u0015', + '\u0016', + '\u0017', + '\u0018', + '\u0019', + '\u001a', + '\u001b', + '\u001c', + '\u001d', + '\u001e', + '\u001f', + ' ', + '!', + '"', + '#', + '\$', + '%', + '&', + '\'', + '(', + ')', + '*', + '+', + ',', + '-', + '.', + '/', + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + ':', + ';', + '<', + '=', + '>', + '?', + '@', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + '[', + '\\', + ']', + '^', + '_', + '`', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + '{', + '|', + '}', + '~', + '\u007f', + '€', + '\u0081', + '‚', + 'ƒ', + '„', + '…', + '†', + '‡', + 'ˆ', + '‰', + 'Š', + '‹', + 'Œ', + '\u008d', + 'Ž', + '\u008f', + '\u0090', + '‘', + '’', + '“', + '”', + '•', + '–', + '—', + '˜', + '™', + 'š', + '›', + 'œ', + '\u009d', + 'ž', + 'Ÿ', + ' ', + '¡', + '¢', + '£', + '¤', + '¥', + '¦', + '§', + '¨', + '©', + 'ª', + '«', + '¬', + '­', + '®', + '¯', + '°', + '±', + '²', + '³', + '´', + 'µ', + '¶', + '·', + '¸', + '¹', + 'º', + '»', + '¼', + '½', + '¾', + '¿', + 'À', + 'Á', + 'Â', + 'Ã', + 'Ä', + 'Å', + 'Æ', + 'Ç', + 'È', + 'É', + 'Ê', + 'Ë', + 'Ì', + 'Í', + 'Î', + 'Ï', + 'Ð', + 'Ñ', + 'Ò', + 'Ó', + 'Ô', + 'Õ', + 'Ö', + '×', + 'Ø', + 'Ù', + 'Ú', + 'Û', + 'Ü', + 'Ý', + 'Þ', + 'ß', + 'à', + 'á', + 'â', + 'ã', + 'ä', + 'å', + 'æ', + 'ç', + 'è', + 'é', + 'ê', + 'ë', + 'ì', + 'í', + 'î', + 'ï', + 'ð', + 'ñ', + 'ò', + 'ó', + 'ô', + 'õ', + 'ö', + '÷', + 'ø', + 'ú', + 'û', + 'ü', + 'ý', + 'þ', + 'ÿ' + ]; } _PdfNumber _getFlagValue() { @@ -2261,6 +2520,9 @@ class _FontStructure { if (decodedText.contains('\u0092')) { decodedText = decodedText.replaceAll('\u0092', '’'); } + isWhiteSpace = (decodedText == null || + decodedText.isEmpty || + decodedText.trimRight() == ''); return decodedText; } @@ -2372,6 +2634,9 @@ class _FontStructure { } } decodedText = skipEscapeSequence(decodedText); + isWhiteSpace = (decodedText == null || + decodedText.isEmpty || + decodedText.trimRight() == ''); return decodedList; } @@ -2564,6 +2829,9 @@ class _FontStructure { break; } decodedText = skipEscapeSequence(decodedText); + isWhiteSpace = (decodedText == null || + decodedText.isEmpty || + decodedText.trimRight() == ''); return decodedList; } @@ -2587,6 +2855,8 @@ class _FontStructure { i++; } } + isWhiteSpace = + (result == null || result.isEmpty || result.trimRight() == ''); return result; } @@ -2738,6 +3008,9 @@ class _FontStructure { if (!isTextExtraction) { encodedText = skipEscapeSequence(encodedText); } + isWhiteSpace = (encodedText == null || + encodedText.isEmpty || + encodedText.trimRight() == ''); return encodedText; } @@ -3963,9 +4236,10 @@ class _FontStructure { } else { if (fontEncoding != 'MacRomanEncoding') { final List charbytes = [decimalValue.toUnsigned(8)]; - temp = utf8.decode(charbytes); - final List tempchar = [ - utf8.decode([decimalValue.toUnsigned(8)]) + temp = _getWindows1252DecodedText(charbytes); + List tempchar; + tempchar = [ + _getWindows1252DecodedText([decimalValue.toUnsigned(8)]) ]; int charvalue = 0; for (final String tempchar1 in tempchar) { @@ -4051,6 +4325,16 @@ class _FontStructure { return decodedText; } + String _getWindows1252DecodedText(List charcodes) { + String result = ''; + charcodes.forEach((int code) { + if (code >= 0 && code < 256) { + result += _windows1252MapTable[code]; + } + }); + return result; + } + // Decodes the HEX encoded string and returns Decoded string. String getHexaDecimalString(String hexEncodedText) { String decodedText = ''; 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 059a2881d..59360d705 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 @@ -581,72 +581,74 @@ class _ImageRenderer { } else { text = structure.decodeTextExtraction(text, _resources.isSameFont()); } - final _TextElement element = _TextElement(text, documentMatrix); - element.fontStyle = structure.fontStyle; - element.fontName = structure.fontName; - element.fontSize = fontSize; - element.textScaling = _textScaling; - element.fontEncoding = structure.fontEncoding; - element.fontGlyphWidths = structure.fontGlyphWidths; - element.defaultGlyphWidth = structure.defaultGlyphWidth; - element._text = text; - element.unicodeCharMapTable = structure.unicodeCharMapTable; - final Map glyphWidths = structure.fontGlyphWidths; - element.characterMapTable = structure.characterMapTable; - element.reverseMapTable = structure.reverseMapTable; - element.structure = structure; - element.isEmbeddedFont = structure.isEmbedded; - element.currentTransformationMatrix = currentTransformationMatrix; - element.textLineMatrix = textMatrix; - element._rise = objects.rise; - element.transformMatrix = documentMatrix; - element.documentMatrix = documentMatrix; - element.fontId = currentFont; - element.octDecMapTable = structure.octDecMapTable; - element.textHorizontalScaling = objects._horizontalScaling; - element.zapfPostScript = structure.zapfPostScript; - element.lineWidth = objects._mitterLength; - element.renderingMode = _renderingMode; - element.pageRotation = pageRotation; - element.zoomFactor = zoomFactor; - element.substitutedFontsList = _substitutedFontsList; - element.wordSpacing = objects.wordSpacing; - element.characterSpacing = objects.characterSpacing; - final _MatrixHelper tempTextMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); - tempTextMatrix.type = _MatrixTypes.identity; - if (_isCurrentPositionChanged) { - _isCurrentPositionChanged = false; - _endTextPosition = currentLocation; - _textElementWidth = element._render( - _graphicsObject, - Offset(_endTextPosition.dx, - _endTextPosition.dy + ((-textLeading) / 4)), - _textScaling, - glyphWidths, - structure._type1GlyphHeight, - structure.differenceTable, - structure.differencesDictionary, - structure.differenceEncoding, - tempTextMatrix); - imageRenderGlyphList.addAll(element.textElementGlyphList); - } else { - _endTextPosition = Offset( - _endTextPosition.dx + _textElementWidth, _endTextPosition.dy); - _textElementWidth = element._render( - _graphicsObject, - Offset( - _endTextPosition.dx, _endTextPosition.dy + (-textLeading / 4)), - _textScaling, - glyphWidths, - structure._type1GlyphHeight, - structure.differenceTable, - structure.differencesDictionary, - structure.differenceEncoding, - tempTextMatrix); - imageRenderGlyphList.addAll(element.textElementGlyphList); - } - if (_isExtractLineCollection) { - extractTextElement.add(element); + if (!structure.isWhiteSpace) { + final _TextElement element = _TextElement(text, documentMatrix); + element.fontStyle = structure.fontStyle; + element.fontName = structure.fontName; + element.fontSize = fontSize; + element.textScaling = _textScaling; + element.fontEncoding = structure.fontEncoding; + element.fontGlyphWidths = structure.fontGlyphWidths; + element.defaultGlyphWidth = structure.defaultGlyphWidth; + element._text = text; + element.unicodeCharMapTable = structure.unicodeCharMapTable; + final Map glyphWidths = structure.fontGlyphWidths; + element.characterMapTable = structure.characterMapTable; + element.reverseMapTable = structure.reverseMapTable; + element.structure = structure; + element.isEmbeddedFont = structure.isEmbedded; + element.currentTransformationMatrix = currentTransformationMatrix; + element.textLineMatrix = textMatrix; + element._rise = objects.rise; + element.transformMatrix = documentMatrix; + element.documentMatrix = documentMatrix; + element.fontId = currentFont; + element.octDecMapTable = structure.octDecMapTable; + element.textHorizontalScaling = objects._horizontalScaling; + element.zapfPostScript = structure.zapfPostScript; + element.lineWidth = objects._mitterLength; + element.renderingMode = _renderingMode; + element.pageRotation = pageRotation; + element.zoomFactor = zoomFactor; + element.substitutedFontsList = _substitutedFontsList; + element.wordSpacing = objects.wordSpacing; + element.characterSpacing = objects.characterSpacing; + final _MatrixHelper tempTextMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); + tempTextMatrix.type = _MatrixTypes.identity; + if (_isCurrentPositionChanged) { + _isCurrentPositionChanged = false; + _endTextPosition = currentLocation; + _textElementWidth = element._render( + _graphicsObject, + Offset(_endTextPosition.dx, + _endTextPosition.dy + ((-textLeading) / 4)), + _textScaling, + glyphWidths, + structure._type1GlyphHeight, + structure.differenceTable, + structure.differencesDictionary, + structure.differenceEncoding, + tempTextMatrix); + imageRenderGlyphList.addAll(element.textElementGlyphList); + } else { + _endTextPosition = Offset( + _endTextPosition.dx + _textElementWidth, _endTextPosition.dy); + _textElementWidth = element._render( + _graphicsObject, + Offset(_endTextPosition.dx, + _endTextPosition.dy + (-textLeading / 4)), + _textScaling, + glyphWidths, + structure._type1GlyphHeight, + structure.differenceTable, + structure.differencesDictionary, + structure.differenceEncoding, + tempTextMatrix); + imageRenderGlyphList.addAll(element.textElementGlyphList); + } + if (_isExtractLineCollection) { + extractTextElement.add(element); + } } } } @@ -677,67 +679,77 @@ class _ImageRenderer { encodedTextBytes[z] = bytes[j]; z++; } - final _TextElement element = _TextElement(text, documentMatrix); - element.fontStyle = structure.fontStyle; - element.fontName = structure.fontName; - element.fontSize = fontSize; - element.textScaling = _textScaling; - element.encodedTextBytes = encodedTextBytes; - element.fontEncoding = structure.fontEncoding; - element.fontGlyphWidths = structure.fontGlyphWidths; - element.defaultGlyphWidth = structure.defaultGlyphWidth; - element.renderingMode = _renderingMode; - element.unicodeCharMapTable = structure.unicodeCharMapTable; - final Map glyphWidths = structure.fontGlyphWidths; - element.cidToGidReverseMapTable = structure.cidToGidReverseMapTable; - element.characterMapTable = structure.characterMapTable; - element.reverseMapTable = structure.reverseMapTable; - // //element.fontfile2Glyph = structure.glyphFontFile2; - element.structure = structure; - element.isEmbeddedFont = structure.isEmbedded; - element.currentTransformationMatrix = currentTransformationMatrix; - element.textLineMatrix = textMatrix; - element._rise = objects.rise; - element.transformMatrix = documentMatrix; - element.documentMatrix = documentMatrix; - element.fontId = currentFont; - element.octDecMapTable = structure.octDecMapTable; - element.textHorizontalScaling = objects._horizontalScaling; - element.zapfPostScript = structure.zapfPostScript; - element.lineWidth = objects._mitterLength; - element.renderingMode = _renderingMode; - element.pageRotation = pageRotation; - element.zoomFactor = zoomFactor; - element.substitutedFontsList = _substitutedFontsList; - if (structure.flags != null) { - element.fontFlag = structure.flags.value.toInt(); - } - element.wordSpacing = objects.wordSpacing; - element.characterSpacing = objects.characterSpacing; - final _MatrixHelper tempTextMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); - tempTextMatrix.type = _MatrixTypes.identity; - if (_isCurrentPositionChanged) { - _isCurrentPositionChanged = false; - _endTextPosition = currentLocation; - } else { - _endTextPosition = Offset( - _endTextPosition.dx + _textElementWidth, _endTextPosition.dy); - } - _textElementWidth = element._renderWithSpacing( - _graphicsObject, - Offset(_endTextPosition.dx, _endTextPosition.dy - fontSize), - decodedList, - characterSpacings, - _textScaling, - glyphWidths, - structure._type1GlyphHeight, - structure.differenceTable, - structure.differencesDictionary, - structure.differenceEncoding, - tempTextMatrix); - imageRenderGlyphList.addAll(element.textElementGlyphList); - if (_isExtractLineCollection) { - extractTextElement.add(element); + if (!structure.isWhiteSpace) { + final List bytes = utf8 + .encode(structure.getEncodedText(text, _resources.isSameFont())); + final Map encodedTextBytes = {}; + int z = 0; + for (int j = 0; j < bytes.length; j = j + 2) { + encodedTextBytes[z] = bytes[j]; + z++; + } + final _TextElement element = _TextElement(text, documentMatrix); + element.fontStyle = structure.fontStyle; + element.fontName = structure.fontName; + element.fontSize = fontSize; + element.textScaling = _textScaling; + element.encodedTextBytes = encodedTextBytes; + element.fontEncoding = structure.fontEncoding; + element.fontGlyphWidths = structure.fontGlyphWidths; + element.defaultGlyphWidth = structure.defaultGlyphWidth; + element.renderingMode = _renderingMode; + element.unicodeCharMapTable = structure.unicodeCharMapTable; + final Map glyphWidths = structure.fontGlyphWidths; + element.cidToGidReverseMapTable = structure.cidToGidReverseMapTable; + element.characterMapTable = structure.characterMapTable; + element.reverseMapTable = structure.reverseMapTable; + // //element.fontfile2Glyph = structure.glyphFontFile2; + element.structure = structure; + element.isEmbeddedFont = structure.isEmbedded; + element.currentTransformationMatrix = currentTransformationMatrix; + element.textLineMatrix = textMatrix; + element._rise = objects.rise; + element.transformMatrix = documentMatrix; + element.documentMatrix = documentMatrix; + element.fontId = currentFont; + element.octDecMapTable = structure.octDecMapTable; + element.textHorizontalScaling = objects._horizontalScaling; + element.zapfPostScript = structure.zapfPostScript; + element.lineWidth = objects._mitterLength; + element.renderingMode = _renderingMode; + element.pageRotation = pageRotation; + element.zoomFactor = zoomFactor; + element.substitutedFontsList = _substitutedFontsList; + if (structure.flags != null) { + element.fontFlag = structure.flags.value.toInt(); + } + element.wordSpacing = objects.wordSpacing; + element.characterSpacing = objects.characterSpacing; + final _MatrixHelper tempTextMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); + tempTextMatrix.type = _MatrixTypes.identity; + if (_isCurrentPositionChanged) { + _isCurrentPositionChanged = false; + _endTextPosition = currentLocation; + } else { + _endTextPosition = Offset( + _endTextPosition.dx + _textElementWidth, _endTextPosition.dy); + } + _textElementWidth = element._renderWithSpacing( + _graphicsObject, + Offset(_endTextPosition.dx, _endTextPosition.dy - fontSize), + decodedList, + characterSpacings, + _textScaling, + glyphWidths, + structure._type1GlyphHeight, + structure.differenceTable, + structure.differencesDictionary, + structure.differenceEncoding, + tempTextMatrix); + imageRenderGlyphList.addAll(element.textElementGlyphList); + if (_isExtractLineCollection) { + extractTextElement.add(element); + } } } } 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 d36accfca..99f80fc40 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 @@ -35,6 +35,16 @@ class PdfTextExtractor { PdfPage _currentPage; _PageResourceLoader _resourceLoader; int _currentPageIndex; + bool _isLayout; + double _characterSpacing; + double _wordSpacing; + _MatrixHelper _textLineMatrix; + _MatrixHelper _textMatrix; + _MatrixHelper _currentTextMatrix; + Rect _tempBoundingRectangle; + bool _hasLeading; + _MatrixHelper _currentTransformationMatrix; + bool _hasBDC; //Public methods /// Extract text from an existing PDF document @@ -55,7 +65,8 @@ class PdfTextExtractor { /// //Dispose the document. /// document.dispose(); /// ``` - String extractText({int startPageIndex, int endPageIndex}) { + String extractText({int startPageIndex, int endPageIndex, bool layoutText}) { + _isLayout = layoutText ?? false; return _extractText(startPageIndex, endPageIndex); } @@ -73,12 +84,12 @@ class PdfTextExtractor { /// //Load an exisiting PDF document. /// PdfDocument document = PdfDocument.fromBase64String(pdfData); /// //Extract text from all pages - /// List textLine = PdfTextExtractor(document).extractTextWithLine(); + /// List textLine = PdfTextExtractor(document).extractTextLines(); /// //Dispose the document. /// document.dispose(); /// ``` - List extractTextWithLine({int startPageIndex, int endPageIndex}) { - return _extractTextWithLine(startPageIndex, endPageIndex); + List extractTextLines({int startPageIndex, int endPageIndex}) { + return _extractTextLines(startPageIndex, endPageIndex); } /// Returns the information of the matched texts in a specific page @@ -120,6 +131,17 @@ class PdfTextExtractor { void _initialize() { _symbolChars = ['(', ')', '[', ']', '<', '>']; _resourceLoader = _PageResourceLoader(); + _characterSpacing = 0; + _wordSpacing = 0; + _currentTextMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); + _textLineMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); + _textMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); + _tempBoundingRectangle = Rect.fromLTWH(0, 0, 0, 0); + _hasLeading = false; + _currentTransformationMatrix = _MatrixHelper(1, 0, 0, 1, 0, 0); + _hasLeading = false; + _hasBDC = false; + _isLayout = false; } String _extractText(int startPageIndex, int endPageIndex) { @@ -141,7 +163,7 @@ class PdfTextExtractor { } } - List _extractTextWithLine(int startPageIndex, int endPageIndex) { + List _extractTextLines(int startPageIndex, int endPageIndex) { if (startPageIndex == null) { if (endPageIndex != null) { throw ArgumentError.value( @@ -227,7 +249,9 @@ class PdfTextExtractor { final bool isContentChanged = _checkContentArray(page); final _PdfRecordCollection recordCollection = _getRecordCollection(page); _PdfPageResources pageResources = _resourceLoader.getPageResources(page); - resultantText = _renderText(recordCollection, pageResources); + resultantText = _isLayout + ? _renderTextAsLayout(recordCollection, pageResources) + : _renderText(recordCollection, pageResources); recordCollection._recordCollection.clear(); pageResources.resources.clear(); if (pageResources._fontCollection != null && @@ -268,7 +292,6 @@ class PdfTextExtractor { double yPos = 0; String pagestring = ''; int lineStartIndex = 0; - int wordEndIndex = 0; TextLine textLine = TextLine._(); if (pagestring == '') { renderer.imageRenderGlyphList.forEach((_Glyph s) { @@ -286,8 +309,8 @@ class PdfTextExtractor { (i == renderer.imageRenderGlyphList.length - 1)) { offsetY = yPos.toInt(); if (textLine.wordCollection.isNotEmpty) { - result.add(_prepareTextLine( - textLine, renderer, lineStartIndex, wordEndIndex)); + result + .add(_prepareTextLine(textLine, renderer, lineStartIndex, i)); } lineStartIndex = i; textLine = TextLine._(); @@ -342,11 +365,19 @@ class PdfTextExtractor { textwords.fontSize = textElement.fontSize; textwords.fontStyle = textElement.fontStyle; textLine.wordCollection.add(textwords); - wordEndIndex = i; } textElement._text = words[x]; if (textElement._text != null && textElement._text != '') { if (x < words.length - 1) { + if (i != 0) { + final Map tempResult = _addSpace(textwords, + renderer, textElement, i, dx, dy, width, height); + dx = tempResult['dx']; + dy = tempResult['dy']; + width = tempResult['width']; + height = tempResult['height']; + textLine.wordCollection.add(tempResult['word']); + } i = i + 1; } if (x < words.length - 1 && @@ -359,6 +390,15 @@ class PdfTextExtractor { if (i <= renderer.imageRenderGlyphList.length - 1) { if (x != words.length - 1 && renderer.imageRenderGlyphList[i].toUnicode == ' ') { + if (i != 0) { + final Map tempResult = _addSpace(textwords, + renderer, textElement, i, dx, dy, width, height); + dx = tempResult['dx']; + dy = tempResult['dy']; + width = tempResult['width']; + height = tempResult['height']; + textLine.wordCollection.add(tempResult['word']); + } i = i + 1; } } @@ -372,8 +412,8 @@ class PdfTextExtractor { if (renderer.extractTextElement.isNotEmpty && k == 0) { offsetY = yPos.toInt(); if (textLine.wordCollection.isNotEmpty) { - result.add(_prepareTextLine( - textLine, renderer, lineStartIndex, wordEndIndex)); + result.add( + _prepareTextLine(textLine, renderer, lineStartIndex, i)); } lineStartIndex = i; textLine = TextLine._(); @@ -387,14 +427,12 @@ class PdfTextExtractor { !result.contains(textLine) && element.renderedText != '' && element.renderedText != ' ') { - result.add( - _prepareTextLine(textLine, renderer, lineStartIndex, wordEndIndex)); + result.add(_prepareTextLine(textLine, renderer, lineStartIndex, i)); textLine = TextLine._(); } } if (textLine.wordCollection.isNotEmpty && !result.contains(textLine)) { - result.add( - _prepareTextLine(textLine, renderer, lineStartIndex, wordEndIndex)); + result.add(_prepareTextLine(textLine, renderer, lineStartIndex, i)); textLine = TextLine._(); } pdfPage._contents._isChanged = isContentChanged; @@ -565,6 +603,51 @@ class PdfTextExtractor { return isContentChanged; } + Map _addSpace( + TextWord textwords, + _ImageRenderer renderer, + _TextElement textElement, + int i, + double dx, + double dy, + double width, + double height) { + textwords = TextWord._(); + final TextGlyph textGlyph = TextGlyph._(); + textGlyph.text = renderer.imageRenderGlyphList[i].toUnicode; + textGlyph.fontName = textElement.fontName; + textGlyph.fontSize = textElement.fontSize; + textGlyph.fontStyle = textElement.fontStyle; + textGlyph.bounds = Rect.fromLTWH( + renderer.imageRenderGlyphList[i].boundingRect.left, + renderer.imageRenderGlyphList[i].boundingRect.top, + renderer.imageRenderGlyphList[i].boundingRect.width, + renderer.imageRenderGlyphList[i].boundingRect.height); + textwords.glyphs.add(textGlyph); + 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) + + renderer.imageRenderGlyphList[i].boundingRect.width; + } else { + width = (renderer.imageRenderGlyphList[i].boundingRect.left - dx) + + renderer.imageRenderGlyphList[i].boundingRect.width; + } + textwords.bounds = Rect.fromLTWH(dx, dy, width, height); + textwords.text = ' '; + textwords.fontName = textElement.fontName; + textwords.fontSize = textElement.fontSize; + textwords.fontStyle = textElement.fontStyle; + return { + 'word': textwords, + 'dx': dx, + 'dy': dy, + 'width': width, + 'height': height + }; + } + Rect _calculatedTextounds( List<_Glyph> glyphs, String text, int index, PdfPage page) { final _Glyph startGlyph = glyphs[index]; @@ -780,6 +863,259 @@ class PdfTextExtractor { return resultantText; } + String _renderTextAsLayout( + _PdfRecordCollection recordCollection, _PdfPageResources pageResources) { + double currentMatrixY = 0; + double prevMatrixY = 0; + double currentY = 0; + double prevY = 0; + double differenceX = 0; + String currentText = ''; + bool hasTj = false; + bool hasTm = false; + _hasBDC = false; + String resultantText = ''; + double textLeading = 0; + double horizontalScaling = 100; + bool hasNoSpacing = false; + bool spaceBetweenWord = false; + _tempBoundingRectangle = Rect.fromLTWH(0, 0, 0, 0); + if (recordCollection != null && + recordCollection._recordCollection.isNotEmpty) { + final List<_PdfRecord> records = recordCollection._recordCollection; + for (int i = 0; i < records.length; i++) { + final _PdfRecord record = records[i]; + final String token = record._operatorName; + final List elements = record._operands; + for (int j = 0; j < _symbolChars.length; j++) { + if (token.contains(_symbolChars[j])) { + token.replaceAll(_symbolChars[j], ''); + } + } + switch (token.trim()) { + case 'Tw': + { + _wordSpacing = double.tryParse(elements[0]); + break; + } + case 'Tc': + { + _characterSpacing = double.tryParse(elements[0]); + break; + } + case 'Tm': + { + final double a = double.tryParse(elements[0]); + final double b = double.tryParse(elements[1]); + final double c = double.tryParse(elements[2]); + final double d = double.tryParse(elements[3]); + final double e = double.tryParse(elements[4]); + final double f = double.tryParse(elements[5]); + _textLineMatrix = _textMatrix = _MatrixHelper(a, b, c, d, e, f); + if (_textMatrix.offsetY == _textLineMatrix.offsetY && + _textMatrix.offsetX != _textLineMatrix.offsetX) { + _textLineMatrix = _textMatrix; + } + if (_textLineMatrix.offsetY != _currentTextMatrix.offsetY || + ((_textLineMatrix.offsetX != _currentTextMatrix.offsetX) && + _hasBDC && + !hasTj)) { + _tempBoundingRectangle = Rect.fromLTWH(0, 0, 0, 0); + _hasBDC = false; + } + break; + } + case 'TL': + { + textLeading = -double.tryParse(elements[0]); + break; + } + case 'cm': + { + 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)) { + resultantText += '\r\n'; + hasTm = false; + } + prevMatrixY = currentMatrixY; + break; + } + case 'BDC': + { + _hasBDC = true; + break; + } + case 'TD': + { + textLeading = double.tryParse(elements[1]); + _textLineMatrix = _textMatrix = _MatrixHelper( + 1, + 0, + 0, + 1, + double.tryParse(elements[0]), + double.tryParse(elements[1])) * + _textLineMatrix; + if (_textLineMatrix.offsetY != _currentTextMatrix.offsetY || + (_hasBDC && + _textLineMatrix.offsetX != _currentTextMatrix.offsetX && + !hasTj)) { + _tempBoundingRectangle = Rect.fromLTWH(0, 0, 0, 0); + _hasBDC = false; + } + break; + } + case 'Td': + { + _textLineMatrix = _textMatrix = _MatrixHelper( + 1, + 0, + 0, + 1, + double.tryParse(elements[0]), + double.tryParse(elements[1])) * + _textLineMatrix; + if (_textLineMatrix.offsetY != _currentTextMatrix.offsetY || + (_hasBDC && + _textLineMatrix.offsetX != _currentTextMatrix.offsetX)) { + _tempBoundingRectangle = Rect.fromLTWH(0, 0, 0, 0); + _hasBDC = false; + } + if ((_textLineMatrix.offsetX - _currentTextMatrix.offsetX) > 0 && + !spaceBetweenWord && + hasTj) { + differenceX = + (_textLineMatrix.offsetX - _currentTextMatrix.offsetX) + .toDouble(); + spaceBetweenWord = true; + } + break; + } + case 'Tz': + { + horizontalScaling = double.tryParse(elements[0]); + break; + } + case 'BT': + { + _textLineMatrix = _textMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); + break; + } + case 'T*': + { + _textLineMatrix = _textMatrix = + _MatrixHelper(1, 0, 0, 1, 0, textLeading) * _textLineMatrix; + break; + } + case 'Tf': + { + _renderFont(elements, pageResources); + break; + } + case 'ET': + { + final double endTextPosition = + (_textLineMatrix.offsetX - _tempBoundingRectangle.right) / 10; + if (_hasLeading && endTextPosition == 0 && hasNoSpacing) { + resultantText += ' '; + _tempBoundingRectangle = Rect.fromLTWH(0, 0, 0, 0); + _hasLeading = false; + } + break; + } + case 'Tj': + case 'TJ': + { + final String currentToken = token.trim(); + currentY = _textMatrix.offsetY; + double difference = 0; + if (_fontSize >= 10) { + difference = ((currentY - prevY) / 10).round().toDouble(); + } else { + difference = + ((currentY - prevY) / _fontSize).round().toDouble(); + } + if (difference < 0) { + difference = -difference; + } + if (spaceBetweenWord) { + if (differenceX > _fontSize) { + differenceX = 0; + } + spaceBetweenWord = false; + } + hasTj = true; + if (prevY != 0 && difference >= 1) { + resultantText += '\r\n'; + } + currentText = currentToken == 'TJ' + ? _renderTextElementTJ( + elements, token, pageResources, horizontalScaling) + : _renderTextElement(elements, token, pageResources); + _currentTextMatrix = _textLineMatrix; + prevY = currentY; + resultantText += currentText; + _textMatrix = _textLineMatrix; + if (currentToken == 'TJ') { + _hasBDC = true; + } + break; + } + case '\'': + { + currentY = _textMatrix.offsetY; + hasNoSpacing = false; + double difference = 0; + if (_fontSize >= 10) { + difference = ((currentY - prevY) / 10).round().toDouble(); + } else { + difference = + ((currentY - prevY) / _fontSize).round().toDouble(); + } + if (difference < 0) { + difference = -difference; + } + _hasLeading = true; + if (prevY != 0 && difference >= 1) { + resultantText += '\r\n'; + } + prevY = currentY; + final int currentXPosition = + (_textLineMatrix.offsetX).toInt().toSigned(64); + final int prevXPosition = + (_currentTextMatrix.offsetX).toInt().toSigned(64); + if ((prevXPosition - currentXPosition) > 0) { + hasNoSpacing = true; + } + _textLineMatrix = _textMatrix = + _MatrixHelper(1, 0, 0, 1, 0, textLeading) * _textLineMatrix; + currentText = _renderTextElement(elements, token, pageResources); + _currentTextMatrix = _textLineMatrix; + resultantText += currentText; + break; + } + case 'Do': + { + final String result = + _getXObject(resultantText, elements, pageResources); + if (result != null && result != '') { + resultantText += result; + } + break; + } + default: + break; + } + } + } + return resultantText; + } + String _skipEscapeSequence(String text) { int index = -1; do { @@ -818,6 +1154,119 @@ class PdfTextExtractor { } } + String _renderTextElementTJ(List elements, String tokenType, + _PdfPageResources pageResources, double horizontalScaling) { + List decodedList = []; + final String text = elements.join(); + String tempText = ''; + if (pageResources.containsKey(_currentFont)) { + _FontStructure fontStructure; + final dynamic returnValue = pageResources[_currentFont]; + if (returnValue != null && returnValue is _FontStructure) { + fontStructure = returnValue; + } + fontStructure.isTextExtraction = true; + if (fontStructure != null) { + fontStructure.fontSize = _fontSize; + } + if (!fontStructure.isEmbedded && + fontStructure._isStandardCJKFont && + fontStructure.font != null) { + decodedList = fontStructure.decodeCjkTextExtractionTJ( + text, pageResources.isSameFont()); + } else { + decodedList = fontStructure.decodeTextExtractionTJ( + text, pageResources.isSameFont()); + } + fontStructure.isTextExtraction = false; + tempText = + _renderTextFromTJ(decodedList, horizontalScaling, fontStructure); + } + return tempText; + } + + String _renderTextFromTJ(List decodedList, double horizontalScaling, + _FontStructure fontStructure) { + String extractedText = ''; + decodedList.forEach((String word) { + final double space = double.tryParse(word); + if (space != null) { + _textLineMatrix = + _updateTextMatrixWithSpacing(space, horizontalScaling); + if ((_textLineMatrix.offsetX - _textMatrix.offsetX).toInt() > 1 && + !_hasBDC) { + extractedText += ' '; + } + } else { + double _characterWidth = 1.0; + if (word != '' && word[word.length - 1] == 's') { + word = word.substring(0, word.length - 1); + } + for (int i = 0; i < word.length; i++) { + final String renderedCharacter = word[i]; + _MatrixHelper transform = _MatrixHelper(1, 0, 0, 1, 0, 0); + if (!fontStructure.isEmbedded && + fontStructure._isStandardFont && + fontStructure.font != null) { + final PdfStandardFont font = fontStructure.font as PdfStandardFont; + _characterWidth = font._getCharWidthInternal(renderedCharacter) * + PdfFont._characterSizeMultiplier; + } else if (!fontStructure.isEmbedded && + fontStructure._isStandardCJKFont && + fontStructure.font != null) { + final PdfCjkStandardFont font = + fontStructure.font as PdfCjkStandardFont; + _characterWidth = font._getCharWidthInternal(renderedCharacter) * + PdfFont._characterSizeMultiplier; + } else { + _characterWidth = + _getCharacterWidth(renderedCharacter, fontStructure); + } + _textMatrix = _getTextRenderingMatrix(horizontalScaling); + final _MatrixHelper identity = _MatrixHelper.identity._clone(); + identity._scale(0.01, 0.01, 0.0, 0.0); + identity._translate(0.0, 1.0); + final _MatrixHelper matrix = transform._clone(); + transform = matrix; + double tempFontSize; + if (_textMatrix.m11 > 0) { + tempFontSize = _textMatrix.m11; + } else if (_textMatrix.m12 != 0 && _textMatrix.m21 != 0) { + if (_textMatrix.m12 < 0) { + tempFontSize = -_textMatrix.m12; + } else { + tempFontSize = _textMatrix.m12; + } + } else { + tempFontSize = _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 += renderedCharacter; + _textLineMatrix = + _updateTextMatrix(_characterWidth, horizontalScaling); + _tempBoundingRectangle = boundingRect; + _textMatrix = _textLineMatrix; + } + } + }); + return extractedText; + } + String _renderTextElement(List elements, String tokenType, _PdfPageResources pageResources) { try { @@ -873,7 +1322,11 @@ class PdfTextExtractor { } else { childResource = _updateFontResources(pageResources); } - result = _renderText(collection, childResource); + if (_isLayout) { + result = _renderTextAsLayout(collection, childResource) + '\r\n'; + } else { + result = _renderText(collection, childResource); + } collection._recordCollection.clear(); } } @@ -890,4 +1343,41 @@ class PdfTextExtractor { }); return resources; } + + _MatrixHelper _updateTextMatrixWithSpacing( + double space, double horizontalScaling) { + final double x = -(space * 0.001 * _fontSize * horizontalScaling / 100); + final Offset point = _textLineMatrix._transform(Offset(0.0, 0.0)); + final Offset point2 = _textLineMatrix._transform(Offset(x, 0.0)); + if (point.dx != point2.dx) { + _textLineMatrix.offsetX = point2.dx; + } else { + _textLineMatrix.offsetY = point2.dy; + } + return _textLineMatrix; + } + + _MatrixHelper _getTextRenderingMatrix(double textHorizontalScaling) { + return _MatrixHelper(_fontSize * (textHorizontalScaling / 100), 0, 0, + -_fontSize, 0, _fontSize) * + _textLineMatrix * + _currentTransformationMatrix; + } + + double _getCharacterWidth(String character, _FontStructure structure) { + final int _charID = character.codeUnitAt(0); + return (structure.fontGlyphWidths != null && + structure.fontType._name == 'TrueType' && + structure.fontGlyphWidths.containsKey(_charID)) + ? structure.fontGlyphWidths[_charID] * 0.001 + : 1.0; + } + + _MatrixHelper _updateTextMatrix( + double characterWidth, double horizontalScaling) { + final double offsetX = + (characterWidth * _fontSize + _characterSpacing + _wordSpacing) * + (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/general/embedded_file.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file.dart new file mode 100644 index 000000000..b345387ee --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file.dart @@ -0,0 +1,78 @@ +part of pdf; + +/// Class which represents embedded file into Pdf document. +class _EmbeddedFile implements _IPdfWrapper { + //Constructor. + /// Initializes a new instance of the [EmbeddedFile] class. + _EmbeddedFile(String fileName, List data) : super() { + ArgumentError.checkNotNull(data, 'data'); + ArgumentError.checkNotNull(fileName, 'fileName'); + this.data = data.toList(); + _initialize(); + this.fileName = fileName; + } + + //Fields. + /// Gets or sets the data. + List data; + final _PdfStream _stream = _PdfStream(); + String _fileName = ''; + final _EmbeddedFileParams _params = _EmbeddedFileParams(); + String _mimeType = ''; + + //Properties. + /// Gets the name of the file. + String get fileName => _fileName; + + /// Sets the name of the file. + set fileName(String value) { + if (_fileName != null) { + _fileName = _getFileName(value); + } + } + + /// Gets the type of the MIME. + String get mimeType => _mimeType; + + /// Sets the type of the MIME. + set mimeType(String value) { + if (_mimeType != value) { + _mimeType = value; + value = value + .replaceAll('#', '#23') + .replaceAll(' ', '#20') + .replaceAll('/', '#2F'); + _stream._setName(_PdfName(_DictionaryProperties.subtype), value); + } + } + + //Implementations. + void _initialize() { + _stream.setProperty(_DictionaryProperties.type, + _PdfName(_DictionaryProperties.embeddedFile)); + _stream.setProperty(_DictionaryProperties.params, _params); + _stream._beginSave = _streamBeginSave; + } + + String _getFileName(String attachmentName) { + final List fileName = attachmentName.split(RegExp(r'[/\\]')); + return fileName[fileName.length - 1]; + } + + void _streamBeginSave(Object sender, _SavePdfPrimitiveArgs ars) { + _stream._clearStream(); + _stream.compress = false; + if (data != null) { + _stream._dataStream = data; + _params._size = data.length; + } + } + + @override + _IPdfPrimitive get _element => _stream; + + @override + set _element(_IPdfPrimitive value) { + throw ArgumentError('primitive element can\'t be set'); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_params.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_params.dart new file mode 100644 index 000000000..a0e457612 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_params.dart @@ -0,0 +1,46 @@ +part of pdf; + +/// Defines additional parameters for the embedded file. +class _EmbeddedFileParams implements _IPdfWrapper { + //Constructor. + _EmbeddedFileParams() : super() { + _creationDate = DateTime.now(); + _modificationDate = DateTime.now(); + } + + //Fields + DateTime _cDate = DateTime.now(); + DateTime _mDate = DateTime.now(); + final _PdfDictionary _dictionary = _PdfDictionary(); + int _fileSize; + + //Properties. + DateTime get _creationDate => _cDate; + set _creationDate(DateTime value) { + _cDate = value; + _dictionary._setDateTime(_DictionaryProperties.creationDate, value); + } + + DateTime get _modificationDate => _mDate; + set _modificationDate(DateTime value) { + _mDate = value; + _dictionary._setDateTime(_DictionaryProperties.modificationDate, value); + } + + // int get _size => _fileSize; + set _size(int value) { + if (_fileSize != value) { + _fileSize = value; + _dictionary._setNumber(_DictionaryProperties.size, _fileSize); + } + } + + //Overrides + @override + _IPdfPrimitive get _element => _dictionary; + + @override + set _element(_IPdfPrimitive value) { + throw ArgumentError('primitive element can\'t be set'); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_specification.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_specification.dart new file mode 100644 index 000000000..9140a72a5 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_specification.dart @@ -0,0 +1,56 @@ +part of pdf; + +/// Represents specification of embedded file. +class _PdfEmbeddedFileSpecification extends _PdfFileSpecificationBase { + //Constructor + /// Initializes a new instance of the [PdfEmbeddedFileSpecification] class + _PdfEmbeddedFileSpecification(String fileName, List data) + : super(fileName) { + ArgumentError.checkNotNull(data, 'data'); + _embeddedFile = _EmbeddedFile(fileName, data); + description = fileName; + } + + //Fields + _EmbeddedFile _embeddedFile; + String _description = ''; + final _PdfDictionary _dict = _PdfDictionary(); + // ignore: prefer_final_fields + PdfAttachmentRelationship _relationship = PdfAttachmentRelationship.source; + + //Properties. + /// Gets the description. + String get description => _description; + + /// Sets the description. + set description(String value) { + if (_description != value) { + _description = value; + _dictionary._setString(_DictionaryProperties.description, _description); + } + } + + //Implementations. + //Initializes instance. + @override + void _initialize() { + super._initialize(); + _dictionary.setProperty(_DictionaryProperties.ef, _dict); + } + + String _getEnumName(dynamic text) { + text = text.toString(); + final int index = text.indexOf('.'); + final String name = text.substring(index + 1); + return name[0].toUpperCase() + name.substring(1); + } + + //Saves object state. + @override + void _save() { + _dict[_DictionaryProperties.f] = _PdfReferenceHolder(_embeddedFile); + final _PdfString str = _PdfString(_formatFileName(fileName, false)); + _dictionary.setProperty(_DictionaryProperties.f, str); + _dictionary.setProperty(_DictionaryProperties.uf, str); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/file_specification_base.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/file_specification_base.dart new file mode 100644 index 000000000..57f369c3c --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/file_specification_base.dart @@ -0,0 +1,62 @@ +part of pdf; + +/// Represents base class for file specification objects. +abstract class _PdfFileSpecificationBase implements _IPdfWrapper { + //Constructor. + /// Initializes a new instance of the [PdfFileSpecificationBase] class. + _PdfFileSpecificationBase(String fileName) { + ArgumentError.checkNotNull(fileName, 'fileName'); + _initialize(); + } + + //Fields + final _PdfDictionary _dictionary = _PdfDictionary(); + + /// Gets or sets the name of the file. + String fileName; + + //Implementations. + //Initializes instance. + void _initialize() { + _dictionary.setProperty( + _DictionaryProperties.type, _PdfName(_DictionaryProperties.filespec)); + _dictionary._beginSave = _dictionaryBeginSave; + } + + //Formats file name to Unix format. + String _formatFileName(String fileName, bool flag) { + const String oldSlash = '\\'; + const String newSlash = '/'; + const String driveDelimiter = ':'; + ArgumentError.checkNotNull(fileName, 'fileName'); + if (fileName.isEmpty) { + throw ArgumentError('fileName, String can not be empty'); + } + String formated = fileName.replaceAll(oldSlash, newSlash); + formated = formated.replaceAll(driveDelimiter, ''); + if (formated.substring(0, 2) == oldSlash) { + formated = formated[0] + formated.substring(2, formated.length); + } + if (formated.substring(0, 1) != newSlash && flag == false) { + formated = formated; + } + return formated; + } + + //Handles the BeginSave event of the m_dictionary control. + void _dictionaryBeginSave(Object sender, _SavePdfPrimitiveArgs ars) { + _save(); + } + + //Saves an instance. + void _save(); + + //Overrides + @override + _IPdfPrimitive get _element => _dictionary; + + @override + set _element(_IPdfPrimitive value) { + throw ArgumentError('primitive element can\'t be set'); + } +} 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 587425b61..0a149206a 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 @@ -11,7 +11,10 @@ abstract class PdfLayoutElement { bool get _raisePageLayouted => endPageLayout != null; //Public methods - /// Draws an element on the Graphics. + /// Draws an element on the graphics or page. + /// + /// If both graphics and page provide in the arguments + /// then page takes more precedence than graphics PdfLayoutResult draw( {PdfGraphics graphics, PdfPage page, 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 89435ca84..fd9bd393e 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 @@ -2,11 +2,6 @@ part of pdf; /// Base class for the main shapes. abstract class PdfShapeElement extends PdfLayoutElement { - // constructor - PdfShapeElement._({PdfPen pen}) { - this.pen = pen; - } - // fields PdfPen _pen; PdfBrush _brush; @@ -35,4 +30,6 @@ abstract class PdfShapeElement extends PdfLayoutElement { final PdfLayoutResult result = layouter._layout(param); return result; } + + _Rectangle _getBoundsInternal(); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/shape_layouter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/shape_layouter.dart index 70eec8cbf..b639e00ed 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 @@ -13,9 +13,12 @@ class _ShapeLayouter extends _ElementLayouter { @override PdfLayoutResult _layoutInternal(_PdfLayoutParams param) { ArgumentError.checkNotNull(param, 'param'); - final PdfPage currentPage = param.page; + PdfPage currentPage = param.page; _Rectangle currentBounds = param.bounds; - final _Rectangle shapeLayoutBounds = _Rectangle.empty; + _Rectangle shapeLayoutBounds = _Rectangle.empty; + if (element is PdfImage) { + shapeLayoutBounds = element._getBoundsInternal(); + } PdfLayoutResult result; _ShapeLayoutResult pageResult = _ShapeLayoutResult(); pageResult._page = currentPage; @@ -32,8 +35,16 @@ class _ShapeLayouter extends _ElementLayouter { endArgs = _raiseEndPageLayout(pageResult); cancel = (endArgs == null) ? false : endArgs.cancel; } - result = _getLayoutResult(pageResult); - break; + if (!pageResult._end && !cancel) { + currentBounds = _getPaginateBounds(param); + shapeLayoutBounds = _getNextShapeBounds(shapeLayoutBounds, pageResult); + currentPage = (endArgs == null || endArgs.nextPage == null) + ? _getNextPage(currentPage) + : endArgs.nextPage; + } else { + result = _getLayoutResult(pageResult); + break; + } } return result; } @@ -66,13 +77,18 @@ class _ShapeLayouter extends _ElementLayouter { !(param.format.breakType == PdfLayoutBreakType.fitElement && !fitToPage && currentPage == param.page); + bool shapeFinished = false; if (canDraw) { final _Rectangle drawRectangle = _getDrawBounds(currentBounds, shapeLayoutBounds); _drawShape(currentPage.graphics, currentBounds.rect, drawRectangle); result._bounds = _getPageResultBounds(currentBounds, shapeLayoutBounds).rect; + shapeFinished = + currentBounds.height.toInt() >= shapeLayoutBounds.height.toInt(); } + result._end = + shapeFinished || param.format.layoutType == PdfLayoutType.onePage; result._page = currentPage; return result; } @@ -100,7 +116,8 @@ class _ShapeLayouter extends _ElementLayouter { _Rectangle _getDrawBounds( _Rectangle currentBounds, _Rectangle shapeLayoutBounds) { - final _Rectangle result = currentBounds; + final _Rectangle result = _Rectangle(currentBounds.x, currentBounds.y, + currentBounds.width, currentBounds.height); result.y -= shapeLayoutBounds.y; result.height += shapeLayoutBounds.y; return result; @@ -128,6 +145,13 @@ class _ShapeLayouter extends _ElementLayouter { return result; } + _Rectangle _getNextShapeBounds( + _Rectangle shapeLayoutBounds, _ShapeLayoutResult pageResult) { + shapeLayoutBounds.y += pageResult._bounds.height; + shapeLayoutBounds.height -= pageResult._bounds.height; + return shapeLayoutBounds; + } + EndPageLayoutArgs _raiseEndPageLayout(_ShapeLayoutResult pageResult) { EndPageLayoutArgs args; if (element._raisePageLayouted) { @@ -146,6 +170,13 @@ class _ShapeLayouter extends _ElementLayouter { } class _ShapeLayoutResult { + //Constructor + _ShapeLayoutResult() { + _bounds = Rect.fromLTWH(0, 0, 0, 0); + _end = false; + } + //Fields PdfPage _page; Rect _bounds; + bool _end; } 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 616ec0247..2352c2c2e 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_bezier_curve.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_bezier_curve.dart @@ -7,8 +7,8 @@ class PdfBezierCurve extends PdfShapeElement { /// with the specified [PdfPen] and [Offset] structure. PdfBezierCurve(Offset startPoint, Offset firstControlPoint, Offset secondControlPoint, Offset endPoint, - {PdfPen pen}) - : super._(pen: pen) { + {PdfPen pen}) { + super.pen = pen; this.startPoint = startPoint; this.firstControlPoint = firstControlPoint; this.secondControlPoint = secondControlPoint; @@ -71,4 +71,9 @@ class PdfBezierCurve extends PdfShapeElement { startPoint, firstControlPoint, secondControlPoint, endPoint, pen: _obtainPen()); } + + @override + _Rectangle _getBoundsInternal() { + return null; + } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_path.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_path.dart index 694548dca..2538f5c6d 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 @@ -11,8 +11,8 @@ class PdfPath extends PdfShapeElement { PdfBrush brush, PdfFillMode fillMode, List points, - List pathTypes}) - : super._(pen: pen) { + List pathTypes}) { + super.pen = pen; if (points != null && pathTypes != null) { addPath(points, pathTypes); } @@ -205,6 +205,7 @@ class PdfPath extends PdfShapeElement { _pathTypes.add(pointType); } + @override _Rectangle _getBoundsInternal() { final List points = _points; _Rectangle bounds = _Rectangle.empty; 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 b371b7c4b..dea762391 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 @@ -33,6 +33,29 @@ class PdfTemplate implements _IPdfWrapper { _isReadonly = true; } + PdfTemplate._(Offset origin, Size size, List stream, + _PdfDictionary resources, bool isLoadedPage) + : super() { + if (size == Size.zero) { + ArgumentError('The size of the new PdfTemplate can\'t be empty.'); + } + ArgumentError.checkNotNull(stream, 'stream'); + _content = _PdfStream(); + if (origin.dx < 0 || origin.dy < 0) { + _setSize(size.width, size.height, origin); + } else { + _setSize(size.width, size.height); + } + _initialize(); + _content._dataStream.addAll(stream); + if (resources != null) { + _content[_DictionaryProperties.resources] = _PdfDictionary(resources); + _resources = _PdfResources(resources); + } + _isLoadedPageTemplate = isLoadedPage; + _isReadonly = true; + } + //Fields _Size _size; _PdfStream _content; @@ -40,6 +63,7 @@ class PdfTemplate implements _IPdfWrapper { _PdfResources _resources; bool _writeTransformation; bool _isReadonly = false; + bool _isLoadedPageTemplate = false; //Properties /// Gets the size of the template. @@ -85,10 +109,16 @@ class PdfTemplate implements _IPdfWrapper { return _resources; } - void _setSize(double width, double height) { - final _Rectangle rectangle = _Rectangle(0, 0, width, height); - _content[_DictionaryProperties.bBox] = _PdfArray.fromRectangle(rectangle); - _size = _Size(width, height); + void _setSize(double width, double height, [Offset origin]) { + if (origin != null) { + final _PdfArray array = _PdfArray([origin.dx, origin.dy, width, height]); + _content[_DictionaryProperties.bBox] = array; + _size = _Size(width, height); + } else { + final _Rectangle rectangle = _Rectangle(0, 0, width, height); + _content[_DictionaryProperties.bBox] = _PdfArray.fromRectangle(rectangle); + _size = _Size(width, height); + } } void _setBounds(Rect bounds) { @@ -98,6 +128,25 @@ class PdfTemplate implements _IPdfWrapper { _size = rect.size; } + void _initialize() { + _content[_DictionaryProperties.type] = + _PdfName(_DictionaryProperties.xObject); + _content[_DictionaryProperties.subtype] = + _PdfName(_DictionaryProperties.form); + } + + void _cloneResources(_PdfCrossTable crossTable) { + if (_resources != null && crossTable != null) { + final List<_PdfReference> prevReference = crossTable._prevReference; + crossTable._prevReference = <_PdfReference>[]; + final _PdfDictionary resourceDict = + _resources._clone(crossTable) as _PdfDictionary; + crossTable._prevReference.addAll(prevReference); + _resources = _PdfResources(resourceDict); + _content[_DictionaryProperties.resources] = resourceDict; + } + } + //_IPdfWrapper members @override _IPdfPrimitive get _element => _content; 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 12da3cba0..8999cc42f 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 @@ -41,6 +41,7 @@ class _UnicodeTrueTypeFont { String _subsetName; Map _usedChars; _PdfDictionary _fontDescriptor; + _PdfStream _cidStream; //Implementation void _initialize(List fontData, double size) { @@ -379,4 +380,54 @@ class _UnicodeTrueTypeFont { double _getCharWidth(String charCode) { return _reader._getCharWidth(charCode); } + + void _initializeCidSet() { + _cidStream = _PdfStream(); + _cidStream._beginSave = _cidBeginSave; + _fontDescriptor._beginSave = _fontDescriptorBeginSave; + } + + //Runs before Cid will be saved. + void _cidBeginSave(Object sender, _SavePdfPrimitiveArgs ars) { + _generateCidSet(); + } + + //Runs before font Dictionary will be saved. + void _fontDescriptorBeginSave(Object sender, _SavePdfPrimitiveArgs ars) { + if ((_usedChars != null && _usedChars.isNotEmpty) && + !_fontDescriptor.containsKey(_DictionaryProperties.cidSet)) { + _fontDescriptor[_DictionaryProperties.cidSet] = + _PdfReferenceHolder(_cidStream); + } + } + + //This is important for PDF/A conformance validation + void _generateCidSet() { + final List dummyBits = [ + 0x80, + 0x40, + 0x20, + 0x10, + 0x08, + 0x04, + 0x02, + 0x01 + ]; + if (_usedChars != null && _usedChars.isNotEmpty) { + final Map glyphChars = _reader._getGlyphChars(_usedChars); + List charBytes; + if (glyphChars.isNotEmpty) { + final List cidChars = glyphChars.keys.toList(); + cidChars.sort(); + final int last = cidChars[cidChars.length - 1]; + charBytes = List((last ~/ 8) + 1); + charBytes.fillRange(0, ((last ~/ 8) + 1), 0); + for (int i = 0; i < cidChars.length; i++) { + final int cid = cidChars[i]; + charBytes[cid ~/ 8] |= dummyBits[cid % 8]; + } + } + _cidStream._write(charBytes); + } + } } 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 1b818b54c..b0e019f25 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 @@ -235,4 +235,16 @@ class PdfBitmap extends PdfImage { } } } + + @override + void _drawInternal(PdfGraphics graphics, _Rectangle bounds) { + ArgumentError.checkNotNull(graphics); + graphics.drawImage( + this, Rect.fromLTWH(0, 0, _width * 0.75, _height * 0.75)); + } + + @override + _Rectangle _getBoundsInternal() { + return _Rectangle(0, 0, width * 0.75, height * 0.75); + } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/pdf_image.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/pdf_image.dart index de4c10258..f7c686da7 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/pdf_image.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/pdf_image.dart @@ -16,7 +16,7 @@ part of pdf; /// //Dispose the document. /// doc.dispose(); /// ``` -abstract class PdfImage implements _IPdfWrapper { +abstract class PdfImage extends PdfShapeElement implements _IPdfWrapper { //Fields double _jpegOrientationAngle; _PdfStream _imageStream; 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 877890dd7..708e41b87 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 @@ -45,6 +45,8 @@ class PdfGraphics { _PdfStringLayoutResult _stringLayoutResult; _PdfAutomaticFieldInfoCollection _automaticFields; Map<_TransparencyData, _PdfTransparency> _trasparencies; + PdfLayer _documentLayer; + //Properties /// Gets or sets the current color space of the document PdfColorSpace colorSpace; @@ -60,7 +62,13 @@ class PdfGraphics { return _transformationMatrix; } - PdfPage get _page => _layer.page; + PdfPage get _page { + if (_documentLayer != null) { + return _documentLayer._page; + } else { + return _layer.page; + } + } /// Gets the automatic fields. _PdfAutomaticFieldInfoCollection get _autoFields { @@ -175,22 +183,26 @@ class PdfGraphics { /// Draws a line connecting the two points specified by the coordinate pairs. void drawLine(PdfPen pen, Offset point1, Offset point2) { + _beginMarkContent(); _stateControl(pen, null, null, null); _streamWriter._beginPath(point1.dx, point1.dy); _streamWriter._appendLineSegment(point2.dx, point2.dy); _streamWriter._strokePath(); + _endMarkContent(); (_getResources() as _PdfResources) ._requireProcset(_DictionaryProperties.pdf); } /// Draws a rectangle specified by a pen, a brush and a Rect structure. void drawRectangle({PdfPen pen, PdfBrush brush, Rect bounds}) { + _beginMarkContent(); _stateControl(pen, brush, null, null); bounds != null ? _streamWriter._appendRectangle( bounds.left, bounds.top, bounds.width, bounds.height) : _streamWriter._appendRectangle(0, 0, 0, 0); _drawPath(pen, brush, PdfFillMode.winding, false); + _endMarkContent(); (_getResources() as _PdfResources) ._requireProcset(_DictionaryProperties.pdf); } @@ -280,7 +292,10 @@ class PdfGraphics { transparency = _trasparencies[transparencyData]; } if (transparency == null) { - transparency = _PdfTransparency(alpha, alphaBrush, mode); + transparency = _PdfTransparency(alpha, alphaBrush, mode, + conformance: _layer != null + ? _page._document._conformanceLevel == PdfConformanceLevel.a1b + : false); _trasparencies[transparencyData] = transparency; } final _PdfResources resources = _getResources(); @@ -292,6 +307,7 @@ class PdfGraphics { } void _drawImage(PdfImage image, Rect rectangle) { + _beginMarkContent(); final _Rectangle bounds = _Rectangle.fromRect(rectangle); PdfGraphicsState beforeOrientation; final int angle = image._jpegOrientationAngle.toInt(); @@ -347,6 +363,7 @@ class PdfGraphics { if (beforeOrientation != null) { restore(beforeOrientation); } + _endMarkContent(); (_getResources() as _PdfResources) ._requireProcset(_DictionaryProperties.grayScaleImage); (_getResources() as _PdfResources) @@ -389,6 +406,32 @@ class PdfGraphics { } void _drawTemplate(PdfTemplate template, Offset location, Size size) { + _beginMarkContent(); + if (_layer != null && + _page._document != null && + _page._document._conformanceLevel != PdfConformanceLevel.none && + template.graphics._currentFont != null && + (template.graphics._currentFont is PdfStandardFont || + template.graphics._currentFont is PdfCjkStandardFont)) { + throw ArgumentError( + 'All the fonts must be embedded in ${_page._document._conformanceLevel.toString()} document.'); + } else if (_layer != null && + _page._document != null && + _page._document._conformanceLevel == PdfConformanceLevel.a1b && + template.graphics._currentFont != null && + template.graphics._currentFont is PdfTrueTypeFont) { + (template.graphics._currentFont as PdfTrueTypeFont) + ._fontInternal + ._initializeCidSet(); + } + if ((_layer != null || _documentLayer != null) && + template._isLoadedPageTemplate) { + _PdfCrossTable crossTable; + crossTable = _page._section._document._crossTable; + if ((template._isReadonly) || (template._isLoadedPageTemplate)) { + template._cloneResources(crossTable); + } + } final double scaleX = (template.size.width > 0) ? size.width / template.size.width : 1; final double scaleY = @@ -396,7 +439,65 @@ class PdfGraphics { final bool hasScale = !(scaleX == 1 && scaleY == 1); final PdfGraphicsState state = save(); final _PdfTransformationMatrix matrix = _PdfTransformationMatrix(); - matrix._translate(location.dx, -(location.dy + size.height)); + if ((_layer != null || _documentLayer != null) && + _page != null && + template._isLoadedPageTemplate) { + bool needTransformation = false; + if (_page._dictionary.containsKey(_DictionaryProperties.cropBox) && + _page._dictionary.containsKey(_DictionaryProperties.mediaBox)) { + _PdfArray cropBox; + _PdfArray mediaBox; + if (_page._dictionary[_DictionaryProperties.cropBox] + is _PdfReferenceHolder) { + cropBox = (_page._dictionary[_DictionaryProperties.cropBox] + as _PdfReferenceHolder) + .object as _PdfArray; + } else { + cropBox = + _page._dictionary[_DictionaryProperties.cropBox] as _PdfArray; + } + if (_page._dictionary[_DictionaryProperties.mediaBox] + is _PdfReferenceHolder) { + mediaBox = (_page._dictionary[_DictionaryProperties.mediaBox] + as _PdfReferenceHolder) + .object as _PdfArray; + } else { + mediaBox = + _page._dictionary[_DictionaryProperties.mediaBox] as _PdfArray; + } + if (cropBox != null && mediaBox != null) { + if (cropBox.toRectangle() == mediaBox.toRectangle()) { + needTransformation = true; + } + } + } + if (_page._dictionary.containsKey(_DictionaryProperties.mediaBox)) { + _PdfArray mBox; + if (_page._dictionary[_DictionaryProperties.mediaBox] + is _PdfReferenceHolder) { + mBox = (_page._dictionary[_DictionaryProperties.mediaBox] + as _PdfReferenceHolder) + .object as _PdfArray; + } else { + mBox = _page._dictionary[_DictionaryProperties.mediaBox] as _PdfArray; + } + if (mBox != null) { + if ((mBox[3] as _PdfNumber).value == 0) { + needTransformation = true; + } + } + } + if ((_page._origin.dx >= 0 && _page._origin.dy >= 0) || + needTransformation) { + matrix._translate(location.dx, -(location.dy + size.height)); + } else if ((_page._origin.dx >= 0 && _page._origin.dy <= 0)) { + matrix._translate(location.dx, -(location.dy + size.height)); + } else { + matrix._translate(location.dx, -(location.dy + 0)); + } + } else { + matrix._translate(location.dx, -(location.dy + size.height)); + } if (hasScale) { matrix._scale(scaleX, scaleY); } @@ -405,6 +506,7 @@ class PdfGraphics { final _PdfName name = resources._getName(template); _streamWriter._executeObject(name); restore(state); + _endMarkContent(); //Transfer automatic fields from template. final PdfGraphics g = template.graphics; @@ -514,10 +616,17 @@ class PdfGraphics { translateTransform(x, y); } - void _setLayer(PdfPageLayer layer) { - _layer = layer; - if (layer.page != null) { - layer.page._beginSave = () { + void _setLayer(PdfPageLayer pageLayer, [PdfLayer pdfLayer]) { + PdfPage page; + if (pageLayer != null) { + _layer = pageLayer; + page = pageLayer.page; + } else if (pdfLayer != null) { + _documentLayer = pdfLayer; + page = pdfLayer._page; + } + if (page != null) { + page._beginSave = () { if (_automaticFields != null) { for (final _PdfAutomaticFieldInfo fieldInfo in _automaticFields._list) { @@ -571,6 +680,7 @@ class PdfGraphics { ArgumentError.checkNotNull(result); ArgumentError.checkNotNull(font); if (!result._isEmpty) { + _beginMarkContent(); _applyStringSettings(font, pen, brush, format, layoutRectangle); final double textScaling = format != null ? format._scalingFactor : 100.0; if (textScaling != _previousTextScaling) { @@ -602,6 +712,7 @@ class PdfGraphics { _streamWriter._endText(); _underlineStrikeoutText( pen, brush, result, font, layoutRectangle, format); + _endMarkContent(); } } @@ -1009,6 +1120,18 @@ class PdfGraphics { void _fontControl(PdfFont font, PdfStringFormat format, bool saveState) { if (font != null) { + if ((font is PdfStandardFont || font is PdfCjkStandardFont) && + _layer != null && + _page._document != null && + _page._document._conformanceLevel != PdfConformanceLevel.none) { + throw ArgumentError( + 'All the fonts must be embedded in ${_page._document._conformanceLevel.toString()} document.'); + } else if (font is PdfTrueTypeFont && + _layer != null && + _page._document != null && + _page._document._conformanceLevel == PdfConformanceLevel.a1b) { + font._fontInternal._initializeCidSet(); + } final PdfSubSuperscript current = format != null ? format.subSuperscript : PdfSubSuperscript.none; final PdfSubSuperscript privious = _currentStringFormat != null @@ -1193,19 +1316,23 @@ class PdfGraphics { void drawBezier(Offset startPoint, Offset firstControlPoint, Offset secondControlPoint, Offset endPoint, {PdfPen pen}) { + _beginMarkContent(); _stateControl(pen, null, null, null); final _PdfStreamWriter sw = _streamWriter; sw._beginPath(startPoint.dx, startPoint.dy); sw._appendBezierSegment(firstControlPoint.dx, firstControlPoint.dy, secondControlPoint.dx, secondControlPoint.dy, endPoint.dx, endPoint.dy); sw._strokePath(); + _endMarkContent(); } /// Draws a GraphicsPath defined by a pen, a brush and path void drawPath(PdfPath path, {PdfPen pen, PdfBrush brush}) { + _beginMarkContent(); _stateControl(pen, brush, null, null); _buildUpPath(path._points, path._pathTypes); _drawPath(pen, brush, path._fillMode, false); + _endMarkContent(); } /// Draws a pie shape defined by an ellipse specified by a Rect structure @@ -1213,21 +1340,25 @@ class PdfGraphics { void drawPie(Rect bounds, double startAngle, double sweepAngle, {PdfPen pen, PdfBrush brush}) { if (sweepAngle != 0) { + _beginMarkContent(); _stateControl(pen, brush, null, null); _constructArcPath(bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height, startAngle, sweepAngle); _streamWriter._appendLineSegment( bounds.left + bounds.width / 2, bounds.top + bounds.height / 2); _drawPath(pen, brush, PdfFillMode.winding, true); + _endMarkContent(); } } /// Draws an ellipse specified by a bounding Rect structure. void drawEllipse(Rect bounds, {PdfPen pen, PdfBrush brush}) { + _beginMarkContent(); _stateControl(pen, brush, null, null); _constructArcPath( bounds.left, bounds.top, bounds.right, bounds.bottom, 0, 360); _drawPath(pen, brush, PdfFillMode.winding, true); + _endMarkContent(); } /// Draws an arc representing a portion of an ellipse specified @@ -1235,15 +1366,18 @@ class PdfGraphics { void drawArc(Rect bounds, double startAngle, double sweepAngle, {PdfPen pen}) { if (sweepAngle != 0) { + _beginMarkContent(); _stateControl(pen, null, null, null); _constructArcPath(bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height, startAngle, sweepAngle); _drawPath(pen, null, PdfFillMode.winding, false); + _endMarkContent(); } } /// Draws a polygon defined by a brush, an array of [Offset] structures. void drawPolygon(List points, {PdfPen pen, PdfBrush brush}) { + _beginMarkContent(); if (points.isEmpty) { return; } @@ -1255,6 +1389,7 @@ class PdfGraphics { points.elementAt(i).dx, points.elementAt(i).dy); } _drawPath(pen, brush, PdfFillMode.winding, true); + _endMarkContent(); } void _buildUpPath(List points, List<_PathPointType> types) { @@ -1396,6 +1531,26 @@ class PdfGraphics { } return pointList; } + + void _beginMarkContent() { + if (_documentLayer != null) { + _documentLayer._beginLayer(this); + } + } + + void _endMarkContent() { + if (_documentLayer != null) { + if (_documentLayer._isEndState && + _documentLayer._parentLayer.isNotEmpty) { + for (int i = 0; i < _documentLayer._parentLayer.length; i++) { + _streamWriter._write('EMC\n'); + } + } + if (_documentLayer._isEndState) { + _streamWriter._write('EMC\n'); + } + } + } } class _TransparencyData { 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 eb1e44510..018a98540 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 @@ -7,6 +7,7 @@ class _PdfResources extends _PdfDictionary { //Fields Map<_IPdfPrimitive, _PdfName> _resourceNames; + final _PdfDictionary _properties = _PdfDictionary(); //Properties Map<_IPdfPrimitive, _PdfName> get _names => _getNames(); 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 846a1045d..25ba7168c 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 @@ -2,17 +2,24 @@ part of pdf; class _PdfTransparency implements _IPdfWrapper { //Constructor - _PdfTransparency(double stroke, double fill, PdfBlendMode mode) { + _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.'); } - if (fill < 0) { throw ArgumentError.value( 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. + if (conformance) { + stroke = 1; + fill = 1; + mode = (mode != PdfBlendMode.normal) ? PdfBlendMode.normal : mode; + } _dictionary[_DictionaryProperties.stroke] = _PdfNumber(stroke); _dictionary[_DictionaryProperties.fill] = _PdfNumber(fill); _dictionary[_DictionaryProperties.bm] = _PdfName(_getBlendMode(mode)); 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 84d0309d3..847a59010 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 @@ -30,6 +30,7 @@ class _CrossTable { int _generationNumber; Map> _allTables; _PdfReferenceHolder _documentCatalog; + _PdfEncryptor _encryptor; //Properties _ObjectInformation operator [](int key) => _returnValue(key); @@ -57,6 +58,15 @@ class _CrossTable { return _documentCatalog; } + _PdfEncryptor get encryptor { + return _encryptor; + } + + set encryptor(_PdfEncryptor value) { + ArgumentError.checkNotNull(value); + _encryptor = value; + } + //Implementation void _initialize() { _generationNumber = 65535; @@ -341,6 +351,9 @@ class _CrossTable { final _ObjectInformation oi = this[archiveNumber]; final _PdfParser parser = oi.parser; archive = parser._parseOffset(oi._offset) as _PdfStream; + if (encryptor != null && !encryptor._encryptOnlyAttachment) { + archive.decrypt(encryptor, archiveNumber); + } archive._decompress(); _archives[archiveNumber] = archive; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/dictionary_properties.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/dictionary_properties.dart index 3830f5e9f..57e33dc3d 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/dictionary_properties.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/dictionary_properties.dart @@ -120,8 +120,8 @@ class _DictionaryProperties { static const String cropBox = 'CropBox'; static const String crossReference = 'xref'; static const String obj = 'obj'; - static const String u = 'u'; - static const String o = 'o'; + static const String u = 'U'; + static const String o = 'O'; static const String catalog = 'Catalog'; static const String version = 'Version'; static const String index = 'Index'; @@ -166,4 +166,53 @@ class _DictionaryProperties { static const String llo = 'LLO'; static const String ft = 'FT'; static const String v = 'V'; + static const String creationDate = 'CreationDate'; + static const String keywords = 'Keywords'; + static const String creator = 'Creator'; + static const String producer = 'Producer'; + static const String info = 'Info'; + static const String r = 'R'; + static const String standard = 'Standard'; + static const String id = 'ID'; + static const String visible = 'Visible'; + static const String ocProperties = 'OCProperties'; + static const String layerID = 'LayerID'; + static const String properties = 'Properties'; + static const String ocgOrder = 'Order'; + static const String ocgOn = 'On'; + static const String ocgOff = 'OFF'; + static const String usageApplication = 'AS'; + static const String category = 'Category'; + static const String event = 'Event'; + static const String ocg = 'OCGs'; + static const String usage = 'Usage'; + static const String print = 'Print'; + static const String defaultView = 'D'; + static const String cf = 'CF'; + static const String cfm = 'CFM'; + static const String stmF = 'StmF'; + static const String strF = 'StrF'; + static const String stdCF = 'StdCF'; + static const String ue = 'UE'; + static const String oe = 'OE'; + static const String perms = 'Perms'; + static const String aesv2 = 'AESV2'; + static const String aesv3 = 'AESV3'; + static const String authEvent = 'AuthEvent'; + static const String docOpen = 'DocOpen'; + static const String metadata = 'Metadata'; + static const String xml = 'XML'; + static const String encryptMetadata = 'EncryptMetadata'; + static const String cidSet = 'CIDSet'; + static const String embeddedFile = 'EmbeddedFile'; + static const String params = 'Params'; + static const String filespec = 'Filespec'; + static const String description = 'Desc'; + static const String uf = 'UF'; + static const String ef = 'EF'; + static const String embeddedFiles = 'EmbeddedFiles'; + static const String afRelationship = 'AFRelationship'; + static const String limits = 'Limits'; + static const String ocgLock = 'Locked'; + static const String af = 'AF'; } 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 e179f76fa..2c9180ef9 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 @@ -14,11 +14,15 @@ class _PdfCrossTable { _document = document; } } + _isColorSpace = false; } - _PdfCrossTable._fromCatalog(int tableCount, _PdfDictionary documentCatalog) { + _PdfCrossTable._fromCatalog(int tableCount, + _PdfDictionary encryptionDictionary, _PdfDictionary documentCatalog) { _storedCount = tableCount; + _encryptorDictionary = encryptionDictionary; _documentCatalog = documentCatalog; _bForceNew = true; + _isColorSpace = false; } //Fields PdfDocument _pdfDocument; @@ -38,6 +42,8 @@ class _PdfCrossTable { int _storedCount; bool _bForceNew = false; Map<_PdfReference, _PdfReference> _mappedReferences; + List<_PdfReference> _prevRef; + bool _isColorSpace; //Properties int get nextObjectNumber { @@ -47,6 +53,15 @@ class _PdfCrossTable { return count++; } + _PdfEncryptor get encryptor { + return _crossTable == null ? null : _crossTable.encryptor; + } + + set encryptor(_PdfEncryptor value) { + ArgumentError.checkNotNull('Encryptor'); + _crossTable.encryptor = value._clone(); + } + _PdfDictionary get documentCatalog { if (_documentCatalog == null && _crossTable != null) { _documentCatalog = @@ -103,6 +118,15 @@ class _PdfCrossTable { _items = _pdfDocument._objects; } + List<_PdfReference> get _prevReference => + (_prevRef != null) ? _prevRef : _prevRef = <_PdfReference>[]; + + set _prevReference(List<_PdfReference> value) { + if (value != null) { + _prevRef = value; + } + } + //Implementation void _initializeCrossTable() { _crossTable = _CrossTable(_data, this); @@ -146,6 +170,7 @@ class _PdfCrossTable { prevXRef.toDouble(), referencePosition.toDouble(), xRefReference); final _PdfStream xRefStream = returnedValue['xRefStream']; xRefReference = returnedValue['reference']; + xRefStream._blockEncryption = true; _doSaveObject(xRefStream, xRefReference, writer); } else { writer._write(_Operators.crossReference); @@ -185,24 +210,60 @@ class _PdfCrossTable { count = 1; _mappedReferences = null; } + _setSecurity(); for (int i = 0; i < objectCollection._count; i++) { final _ObjectInfo objInfo = objectCollection[i]; if (objInfo._modified || _bForceNew) { final _IPdfPrimitive obj = objInfo._object; + final _IPdfPrimitive reference = objInfo._reference; + if (reference == null) { + final _PdfReference ref = _getReference(obj); + if (ref != null) { + objInfo._reference = ref; + } + } _saveIndirectObject(obj, writer); } } } + void _setSecurity() { + final PdfSecurity security = _document.security; + trailer.encrypt = false; + if (security._encryptor.encrypt) { + _PdfDictionary securityDictionary = encryptorDictionary; + if (securityDictionary == null) { + securityDictionary = _PdfDictionary(); + securityDictionary.encrypt = false; + _document._objects._add(securityDictionary); + securityDictionary.position = -1; + } + securityDictionary = + security._encryptor._saveToDictionary(securityDictionary); + trailer[_DictionaryProperties.id] = security._encryptor.fileID; + trailer[_DictionaryProperties.encrypt] = + _PdfReferenceHolder(securityDictionary); + } + } + void _saveIndirectObject(_IPdfPrimitive object, _PdfWriter writer) { ArgumentError.checkNotNull(object, 'object'); ArgumentError.checkNotNull(writer, 'writer'); final _PdfReference reference = _getReference(object); if (object is _PdfCatalog) { trailer[_DictionaryProperties.root] = reference; + //NOTE: This is needed to get PDF/A Conformance. + if (_document != null && + _document._conformanceLevel != PdfConformanceLevel.none) { + trailer[_DictionaryProperties.id] = + _document.security._encryptor.fileID; + } } + _document._currentSavingObject = reference; + bool archive = false; + archive = (object is _PdfDictionary) ? object._archive : true; final bool allowedType = - !((object is _PdfStream) || (object is _PdfCatalog)); + !((object is _PdfStream) || !(archive) || (object is _PdfCatalog)); bool sigFlag = false; if (object is _PdfDictionary && _document.fileStructure.crossReferenceType == @@ -474,6 +535,7 @@ class _PdfCrossTable { } trailerDictionary[_DictionaryProperties.size] = _PdfNumber(_count); trailerDictionary = _PdfDictionary(trailerDictionary); + trailerDictionary.encrypt = false; trailerDictionary.save(writer); } @@ -507,6 +569,7 @@ class _PdfCrossTable { } _IPdfPrimitive _getObject(_IPdfPrimitive pointer) { + bool isEncryptedMetadata = true; _IPdfPrimitive result = pointer; if (pointer is _PdfReferenceHolder) { result = pointer.object; @@ -535,6 +598,23 @@ class _PdfCrossTable { } } result = obj; + if (obj != null && obj is _PdfDictionary) { + final _PdfDictionary dictionary = obj; + if (dictionary.containsKey(_DictionaryProperties.type)) { + final _IPdfPrimitive primitive = + dictionary[_DictionaryProperties.type]; + if (primitive != null && + primitive is _PdfName && + primitive._name == _DictionaryProperties.metadata) { + if (encryptor != null) { + isEncryptedMetadata = encryptor.encryptMetadata; + } + } + } + } + if (_document._isEncrypted && isEncryptedMetadata) { + _decrypt(result); + } } if (pointer is _PdfReference) { _objNumbers.removeLast(); @@ -596,6 +676,7 @@ class _PdfCrossTable { reference = _PdfReference(nextObjectNumber, 0); ai._reference = reference; } + _document._currentSavingObject = reference; _registerObject(reference, position: writer._position); _doSaveObject(ai._archive, reference, writer); } @@ -677,6 +758,7 @@ class _PdfCrossTable { if (prevXRef == 0 && xRefStream.containsKey(_DictionaryProperties.prev)) { xRefStream.remove(_DictionaryProperties.prev); } + xRefStream.encrypt = false; return { 'xRefStream': xRefStream, 'reference': reference @@ -754,6 +836,48 @@ class _PdfCrossTable { 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; + if (!dic.decrypted) { + dic._items.forEach((_PdfName key, _IPdfPrimitive element) { + _decrypt(element); + }); + if (obj is _PdfStream) { + final _PdfStream stream = obj; + if (_document._isEncrypted && + stream != null && + !stream.decrypted && + _objNumbers.isNotEmpty && + encryptor != null) { + stream.decrypt(encryptor, _objNumbers.last._objNum); + } + } + } + } else if (obj is _PdfArray) { + final _PdfArray array = obj; + array._elements.forEach((_IPdfPrimitive element) { + if (element is _PdfName) { + final _PdfName name = element; + if (name._name == 'Indexed') { + _isColorSpace = true; + } + } + _decrypt(element); + }); + _isColorSpace = false; + } else if (obj is _PdfString) { + final _PdfString str = obj; + if (!str.decrypted && (!str._isHex || _isColorSpace)) { + if (_document._isEncrypted && _objNumbers.isNotEmpty) { + obj.decrypt(encryptor, _objNumbers.last._objNum); + } + } + } + } + } } /// Represents a registered object. 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 0e70c93f9..77215db17 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 @@ -546,9 +546,13 @@ class _PdfParser { _match(_next, _TokenType.hexStringStart); _advance(); String sb = ''; + bool isHex = true; while (_next != _TokenType.hexStringEnd) { String text = _lexer.text; - if (_next == _TokenType.hexStringWeirdEscape) { + if (_next == _TokenType.hexStringWeird) { + isHex = false; + } else if (_next == _TokenType.hexStringWeirdEscape) { + isHex = false; text = text.substring(1); } sb += text; @@ -556,7 +560,7 @@ class _PdfParser { } _match(_next, _TokenType.hexStringEnd); _advance(); - final _PdfString result = _PdfString(sb); + final _PdfString result = _PdfString(sb, !isHex); return result; } 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 5a526a1e9..37cd40da5 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 @@ -181,7 +181,7 @@ class _PdfStreamWriter implements _IPdfWriter { void _writeText(dynamic value) { if (value is _PdfString) { - _stream._write(value._pdfEncode()); + _stream._write(value._pdfEncode(null)); } else if (value is List) { _stream._write(_PdfString.stringMark[0]); _stream._write(value); 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 new file mode 100644 index 000000000..e47694a28 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer.dart @@ -0,0 +1,449 @@ +part of pdf; + +/// The [PdfLayer] used to create layers in PDF document. +class PdfLayer implements _IPdfWrapper { + // Constructor + PdfLayer._() { + _clipPageTemplates = true; + _content = _PdfStream(); + _dictionary = _PdfDictionary(); + } + + // Fields + bool _clipPageTemplates = true; + _PdfStream _content; + PdfGraphics _graphics; + PdfLayer _layer; + String _layerId; + _PdfReferenceHolder _referenceHolder; + final Map _graphicsMap = + {}; + final Map _pageGraphics = {}; + bool _isEmptyLayer = false; + final List _parentLayer = []; + String _name; + bool _visible = true; + _PdfDictionary _dictionary; + bool _isEndState = false; + final List _pages = []; + PdfPage _page; + PdfDocument _document; + PdfLayerCollection _layerCollection; + _PdfDictionary _printOption; + _PdfDictionary _usage; + PdfLayer _parent; + final List _child = []; + final _PdfArray _sublayer = _PdfArray(); + final List _xobject = []; + bool _pageParsed = false; + bool _isPresent; + + // Properties + /// Gets the name of the layer + String get name => _name; + + /// Sets the name of the layer + set name(String value) { + _name = value; + if (_dictionary != null && _name != null && _name != '') { + _dictionary.setProperty(_DictionaryProperties.name, _PdfString(_name)); + } + } + + /// Gets the visibility of the page layer. + bool get visible { + if (_dictionary != null && + _dictionary.containsKey(_DictionaryProperties.visible)) { + _visible = + (_dictionary[_DictionaryProperties.visible] as _PdfBoolean).value; + } + return _visible; + } + + /// Sets the visibility of the page layer. + set visible(bool value) { + _visible = value; + if (_dictionary != null) { + _dictionary[_DictionaryProperties.visible] = _PdfBoolean(value); + } + } + + /// Gets the collection of child [PdfLayer] + PdfLayerCollection get layers { + _layerCollection ??= PdfLayerCollection._withLayer(_document, _layer); + return _layerCollection; + } + + // Implementation + /// Initializes Graphics context of the layer. + PdfGraphics createGraphics(PdfPage page) { + _page = page; + if (_graphics == null) { + page._contents._add(_PdfReferenceHolder(_layer)); + } + final _PdfResources resource = page._getResources(); + if (_layer._layerId == null || _layer._layerId.isEmpty) { + _layer._layerId = 'OCG_' + _PdfResources._globallyUniqueIdentifier; + } + if (resource != null && + resource.containsKey(_DictionaryProperties.properties)) { + final _PdfDictionary propertie = + resource[_DictionaryProperties.properties] as _PdfDictionary; + if (propertie != null) { + if (_layer._layerId[0] == '/') { + _layer._layerId = _layer._layerId.substring(1); + } + propertie[_layer._layerId] = _layer._referenceHolder; + } else { + resource._properties[_layer._layerId] = _layer._referenceHolder; + resource[_DictionaryProperties.properties] = resource._properties; + } + } else { + resource._properties[_layer._layerId] = _layer._referenceHolder; + resource[_DictionaryProperties.properties] = resource._properties; + } + + if (_graphics == null) { + final Function resources = page._getResources; + bool isPageHasMediaBox = false; + if (page._dictionary + .containsKey(_PdfName(_DictionaryProperties.mediaBox))) { + isPageHasMediaBox = true; + } + double llx = 0; + double lly = 0; + double urx = 0; + double ury = 0; + final _PdfArray mediaBox = page._dictionary._getValue( + _DictionaryProperties.mediaBox, _DictionaryProperties.parent); + if (mediaBox != null) { + // Lower Left X co-ordinate Value. + llx = (mediaBox[0] as _PdfNumber).value.toDouble(); + // Lower Left Y co-ordinate value. + lly = (mediaBox[1] as _PdfNumber).value.toDouble(); + // Upper right X co-ordinate value. + urx = (mediaBox[2] as _PdfNumber).value.toDouble(); + // Upper right Y co-ordinate value. + ury = (mediaBox[3] as _PdfNumber).value.toDouble(); + } + _PdfArray cropBox; + if (page._dictionary.containsKey(_DictionaryProperties.cropBox)) { + cropBox = page._dictionary._getValue( + _DictionaryProperties.cropBox, _DictionaryProperties.parent); + final double cropX = (cropBox[0] as _PdfNumber).value.toDouble(); + final double cropY = (cropBox[1] as _PdfNumber).value.toDouble(); + final double cropRX = (cropBox[2] as _PdfNumber).value.toDouble(); + final double cropRY = (cropBox[3] as _PdfNumber).value.toDouble(); + if ((cropX < 0 || cropY < 0 || cropRX < 0 || cropRY < 0) && + (cropY.abs().floor() == page.size.height.abs().floor()) && + (cropX.abs().floor()) == page.size.width.abs().floor()) { + final Size pageSize = + Size([cropX, cropRX].reduce(max), [cropY, cropRY].reduce(max)); + _graphics = PdfGraphics._(pageSize, resources, _content); + } else { + _graphics = PdfGraphics._(page.size, resources, _content); + _graphics._cropBox = cropBox; + } + } else if ((llx < 0 || lly < 0 || urx < 0 || ury < 0) && + (lly.abs().floor() == page.size.height.abs().floor()) && + (urx.abs().floor() == page.size.width.abs().floor())) { + final Size pageSize = + Size([llx, urx].reduce(max), [lly, ury].reduce(max)); + if (pageSize.width <= 0 || pageSize.height <= 0) { + _graphics = PdfGraphics._(pageSize, resources, _content); + } + } else { + _graphics = PdfGraphics._(page.size, resources, _content); + } + if (isPageHasMediaBox) { + _graphics._mediaBoxUpperRightBound = ury; + } + if (page != null && !page._isLoadedPage) { + final PdfSectionCollection sectionCollection = page._section._parent; + if (sectionCollection != null) { + _graphics.colorSpace = sectionCollection._document.colorSpace; + } + } + if (!_graphicsMap.containsKey(_graphics)) { + _graphicsMap[_graphics] = _graphics; + } + if (!_pageGraphics.containsKey(page)) { + _pageGraphics[page] = _graphics; + } + _content._beginSave = _beginSaveContent; + } else { + if (!_pages.contains(page)) { + _graphicsContent(page); + } else if (_pageGraphics.containsKey(page)) { + _graphics = _pageGraphics[page]; + return _graphics; + } + } + _graphics._streamWriter._write(_Operators.newLine); + _graphics.save(); + _graphics._initializeCoordinates(); + if (_graphics._hasTransparencyBrush) { + _graphics._setTransparencyGroup(page); + } + if (page != null && + page._isLoadedPage && + (page._rotation != PdfPageRotateAngle.rotateAngle0 || + page._dictionary.containsKey(_DictionaryProperties.rotate))) { + _PdfArray cropBox; + if (page._dictionary.containsKey(_DictionaryProperties.cropBox)) { + cropBox = page._dictionary._getValue( + _DictionaryProperties.cropBox, _DictionaryProperties.parent); + } + _updatePageRotation(page, _graphics, cropBox); + } + if (page != null && !page._isLoadedPage) { + final _Rectangle clipRect = page._section._getActualBounds(page, true); + if (_clipPageTemplates) { + if (page._origin.dx >= 0 && page._origin.dy >= 0) { + _graphics._clipTranslateMarginsWithBounds(clipRect); + } + } else { + final PdfMargins margins = page._section.pageSettings.margins; + _graphics._clipTranslateMargins(clipRect.x, clipRect.y, margins.left, + margins.top, margins.right, margins.bottom); + } + } + if (!_pages.contains(page)) { + _pages.add(page); + } + _graphics._setLayer(null, this); + return _graphics; + } + + void _updatePageRotation( + PdfPage page, PdfGraphics graphics, _PdfArray cropBox) { + _PdfNumber rotation; + if (page._dictionary.containsKey(_DictionaryProperties.rotate)) { + rotation = page._dictionary[_DictionaryProperties.rotate]; + rotation ??= rotation = _PdfCrossTable._dereference( + page._dictionary[_DictionaryProperties.rotate]); + } else if (page._rotation != PdfPageRotateAngle.rotateAngle0) { + if (page._rotation == PdfPageRotateAngle.rotateAngle90) { + rotation = _PdfNumber(90); + } else if (page._rotation == PdfPageRotateAngle.rotateAngle180) { + rotation = _PdfNumber(180); + } else if (page._rotation == PdfPageRotateAngle.rotateAngle270) { + rotation = _PdfNumber(270); + } + } + if (rotation.value == 90) { + graphics.translateTransform(0, page.size.height); + graphics.rotateTransform(-90); + if (cropBox != null) { + final double height = (cropBox[3] as _PdfNumber).value.toDouble(); + final Size cropBoxSize = Size( + (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()); + if (page.size.height < cropBoxSize.height) { + graphics._clipBounds.size = _Size(page.size.height - cropBoxOffset.dy, + cropBoxSize.width - cropBoxOffset.dx); + } else { + graphics._clipBounds.size = _Size( + cropBoxSize.height - cropBoxOffset.dy, + cropBoxSize.width - cropBoxOffset.dx); + } + } else { + graphics._clipBounds.size = _Size(page.size.height, page.size.width); + } + } else if (rotation.value == 180) { + graphics.translateTransform(page.size.width, page.size.height); + graphics.rotateTransform(-180); + } else if (rotation.value == 270) { + graphics.translateTransform(page.size.width, 0); + graphics.rotateTransform(-270); + graphics._clipBounds.size = _Size(page.size.height, page.size.width); + } + } + + void _graphicsContent(PdfPage page) { + final _PdfStream stream = _PdfStream(); + _graphics = PdfGraphics._(page.size, page._getResources, stream); + page._contents._add(_PdfReferenceHolder(stream)); + stream._beginSave = _beginSaveContent; + if (!_graphicsMap.containsKey(_graphics)) { + _graphicsMap[_graphics] = _graphics; + } + if (!_pageGraphics.containsKey(page)) { + _pageGraphics[page] = _graphics; + } + } + + void _beginSaveContent(Object sender, _SavePdfPrimitiveArgs e) { + if (_layer._graphicsMap != null) { + bool flag = false; + PdfGraphics keyValue; + _graphicsMap.forEach((PdfGraphics key, PdfGraphics values) { + if (!flag) { + _graphics = key; + if (!_isEmptyLayer) { + _beginLayer(_graphics); + _graphics._endMarkContent(); + } + _graphics._streamWriter + ._write(_Operators.restoreState + _Operators.newLine); + keyValue = key; + flag = true; + } + }); + if (keyValue != null) { + _graphicsMap.remove(keyValue); + } + } + } + + void _beginLayer(PdfGraphics currentGraphics) { + if (_graphicsMap != null) { + if (_graphicsMap.containsKey(currentGraphics)) { + _graphics = _graphicsMap[currentGraphics]; + } else { + _graphics = currentGraphics; + } + } + if (_graphics != null) { + if (_name != null && _name != '') { + _isEmptyLayer = true; + if (_parentLayer.isNotEmpty) { + for (int i = 0; i < _parentLayer.length; i++) { + _graphics._streamWriter + ._write('/OC /' + _parentLayer[i]._layerId + ' BDC\n'); + } + } + if (name != null && name != '') { + _graphics._streamWriter._write('/OC /' + _layerId + ' BDC\n'); + _isEndState = true; + } else { + _content._write('/OC /' + _layerId + ' BDC\n'); + } + } + } + } + + void _parsingLayerPage() { + if (_document != null && _document._isLoadedDocument) { + for (int i = 0; i < _document.pages.count; i++) { + final _PdfDictionary pageDictionary = _document.pages[i]._dictionary; + final PdfPage page = _document.pages[i]; + if (pageDictionary.containsKey(_DictionaryProperties.resources)) { + final _PdfDictionary resources = _PdfCrossTable._dereference( + pageDictionary[_DictionaryProperties.resources]) + as _PdfDictionary; + if (resources != null && + (resources.containsKey(_DictionaryProperties.properties)) || + (resources.containsKey(_DictionaryProperties.xObject))) { + final _PdfDictionary properties = _PdfCrossTable._dereference( + resources[_DictionaryProperties.properties]) as _PdfDictionary; + final _PdfDictionary xObject = _PdfCrossTable._dereference( + resources[_DictionaryProperties.xObject]) as _PdfDictionary; + if (properties != null) { + properties._items.forEach((_PdfName key, _IPdfPrimitive value) { + if (value is _PdfReferenceHolder) { + final _PdfDictionary dictionary = + value.object as _PdfDictionary; + _parsingDictionary(dictionary, value, page, key); + } + }); + if (properties._items.isEmpty) { + _pageParsed = true; + } + } + if (xObject != null) { + xObject._items.forEach((_PdfName key, _IPdfPrimitive value) { + _PdfReferenceHolder reference = value as _PdfReferenceHolder; + _PdfDictionary dictionary = reference.object as _PdfDictionary; + if (dictionary.containsKey('OC')) { + final _PdfName layerID = key; + reference = dictionary['OC'] as _PdfReferenceHolder; + dictionary = _PdfCrossTable._dereference(dictionary['OC']) + as _PdfDictionary; + final bool isPresent = + _parsingDictionary(dictionary, reference, page, layerID); + if (isPresent) { + _layer._xobject.add(layerID._name); + } + } + }); + if (xObject._items.isEmpty) { + _pageParsed = true; + } + } + } + } + } + } + } + + bool _parsingDictionary(_PdfDictionary dictionary, + _PdfReferenceHolder reference, PdfPage page, _PdfName layerID) { + if (_isPresent == null || !_isPresent) { + _isPresent = false; + if (!dictionary.containsKey(_DictionaryProperties.name) && + dictionary.containsKey(_DictionaryProperties.ocg)) { + if (dictionary.containsKey(_DictionaryProperties.ocg)) { + final _PdfArray pdfArray = + _PdfCrossTable._dereference(dictionary[_DictionaryProperties.ocg]) + as _PdfArray; + if (pdfArray == null) { + reference = + dictionary[_DictionaryProperties.ocg] as _PdfReferenceHolder; + dictionary = _PdfCrossTable._dereference( + dictionary[_DictionaryProperties.ocg]) as _PdfDictionary; + if (dictionary != null && + dictionary.containsKey(_DictionaryProperties.name)) { + _isPresent = _setLayerPage(reference, page, layerID); + } + } else { + for (int a = 0; a < pdfArray.count; a++) { + if (pdfArray[a] is _PdfReferenceHolder) { + reference = pdfArray[a] as _PdfReferenceHolder; + dictionary = reference.object as _PdfDictionary; + _isPresent = _setLayerPage(reference, page, layerID); + } + } + } + } + } else if (dictionary.containsKey(_DictionaryProperties.name)) { + _isPresent = _setLayerPage(reference, page, layerID); + } + return _isPresent; + } else { + return false; + } + } + + bool _setLayerPage( + _PdfReferenceHolder reference, PdfPage page, _PdfName layerID) { + bool isPresent = false; + if (_layer._referenceHolder != null) { + if (identical(_layer._referenceHolder, reference) || + identical(_layer._referenceHolder._object, reference._object) || + _layer._referenceHolder?.reference?._objNum == + reference?.reference?._objNum) { + _layer._pageParsed = true; + isPresent = true; + _layer._layerId = layerID._name; + _layer._page = page; + if (!_layer._pages.contains(page)) { + _layer._pages.add(page); + } + } + } + return isPresent; + } + + //_IPdfWrapper elements + @override + _IPdfPrimitive get _element => _content; + @override + //ignore: unused_element + set _element(_IPdfPrimitive value) { + _content = value; + } +} 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 new file mode 100644 index 000000000..e1a6e346d --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer_collection.dart @@ -0,0 +1,763 @@ +part of pdf; + +/// The class provides methods and properties to handle the collections of [PdfLayer] +class PdfLayerCollection extends PdfObjectCollection { + // Contructor + PdfLayerCollection._(PdfDocument document) { + ArgumentError.checkNotNull(document, 'document'); + _document = document; + _sublayer = false; + if (document._isLoadedDocument) { + _getDocumentLayer(document); + } + } + + PdfLayerCollection._withLayer(PdfDocument document, PdfLayer layer) { + _document = document; + _parent = layer; + _sublayer = true; + } + + // Fields + PdfDocument _document; + bool _sublayer; + PdfLayer _parent; + final _PdfDictionary _optionalContent = _PdfDictionary(); + final Map<_PdfReferenceHolder, PdfLayer> _layerDictionary = + <_PdfReferenceHolder, PdfLayer>{}; + int _bdcCount = 0; + + //Properties + /// Gets [PdfLayer] by its index from [PdfLayerCollection]. + PdfLayer operator [](int index) { + return _list[index] as PdfLayer; + } + + // Implementation + /// Creates a new [PdfLayer] with name and adds it to the end of the collection. + PdfLayer add({String name, bool visible}) { + final PdfLayer layer = PdfLayer._(); + if (name != null) { + layer.name = name; + } + layer._document = _document; + if (visible != null) { + layer.visible = visible; + } + layer._layerId = 'OCG_' + _PdfResources._globallyUniqueIdentifier; + layer._layer = layer; + _add(layer); + return layer; + } + + /// 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}) { + if (layer != null) { + _removeLayer(layer, isRemoveGraphicalContent); + _list.remove(layer); + } else if (name != null) { + bool isFind = false; + for (int i = 0; i < _list.length; i++) { + final PdfLayer layer = _list[i] as PdfLayer; + if (layer.name == name) { + isFind = true; + _removeLayer(layer, isRemoveGraphicalContent); + _list.remove(layer); + i = i - 1; + } + } + if (!isFind) { + ArgumentError.value('Given layerName is not found'); + } + } else { + ArgumentError.value('layer or layerName must be required'); + } + } + + /// Removes layer by its index from collections and may also remove graphical content if isRemoveGraphicalContent is true. + void removeAt(int index, [bool isRemoveGraphicalContent = false]) { + if (index < 0 || index > _list.length - 1) { + ArgumentError.value( + '$index Value can not be less 0 and greater List.Count - 1'); + } + final PdfLayer layer = this[index]; + _list.removeAt(index); + if (layer != null) { + _removeLayer(layer, isRemoveGraphicalContent); + } + } + + /// Clears layers from the [PdfLayerCollection]. + void clear() { + for (int i = 0; i < _list.length; i++) { + final PdfLayer layer = this[i]; + _removeLayer(layer, true); + } + _list.clear(); + } + + int _add(PdfLayer layer) { + ArgumentError.checkNotNull(layer, 'layer'); + _list.add(layer); + final int index = count - 1; + if (_document is PdfDocument) { + _createLayer(layer); + } + layer._layer = layer; + return index; + } + + void _createLayer(PdfLayer layer) { + final _PdfDictionary ocProperties = _PdfDictionary(); + ocProperties[_DictionaryProperties.ocg] = + _createOptionalContentDictionary(layer); + ocProperties[_DictionaryProperties.defaultView] = + _createOptionalContentViews(layer); + _document._catalog[_DictionaryProperties.ocProperties] = ocProperties; + _document._catalog + .setProperty(_DictionaryProperties.ocProperties, ocProperties); + } + + _IPdfPrimitive _createOptionalContentDictionary(PdfLayer layer) { + final _PdfDictionary dictionary = _PdfDictionary(); + dictionary[_DictionaryProperties.name] = _PdfString(layer.name); + dictionary[_DictionaryProperties.type] = _PdfName('OCG'); + dictionary[_DictionaryProperties.layerID] = _PdfName(layer._layerId); + dictionary[_DictionaryProperties.visible] = _PdfBoolean(layer.visible); + + layer._usage = _setPrintOption(layer); + dictionary[_DictionaryProperties.usage] = _PdfReferenceHolder(layer._usage); + _document._printLayer._add(_PdfReferenceHolder(dictionary)); + + final _PdfReferenceHolder reference = _PdfReferenceHolder(dictionary); + _document._primitive._add(reference); + layer._referenceHolder = reference; + layer._dictionary = dictionary; + + // Order of the layers + final _PdfDictionary ocProperties = _PdfCrossTable._dereference( + _document._catalog[_DictionaryProperties.ocProperties]) + as _PdfDictionary; + _createSublayer(ocProperties, reference, layer); + if (layer.visible) { + _document._on._add(reference); + } else { + _document._off._add(reference); + } + return _document._primitive; + } + + _PdfDictionary _setPrintOption(PdfLayer layer) { + final _PdfDictionary _usage = _PdfDictionary(); + layer._printOption = _PdfDictionary(); + layer._printOption[_DictionaryProperties.subtype] = _PdfName('Print'); + _usage[_DictionaryProperties.print] = + _PdfReferenceHolder(layer._printOption); + return _usage; + } + + void _createSublayer(_PdfDictionary ocProperties, + _PdfReferenceHolder reference, PdfLayer layer) { + if (_sublayer == false) { + if (ocProperties != null) { + _PdfArray order; + final _PdfDictionary defaultview = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + if (defaultview != null) { + order = _PdfCrossTable._dereference( + defaultview[_DictionaryProperties.ocgOrder]) as _PdfArray; + } + if (_document._order != null && order != null) { + _document._order = order; + } + _document._order._add(reference); + } else { + _document._order._add(reference); + } + } else { + layer._parent = _parent; + if (ocProperties != null) { + _PdfArray order; + final _PdfDictionary defaultview = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + if (defaultview != null) { + order = _PdfCrossTable._dereference( + defaultview[_DictionaryProperties.ocgOrder]) as _PdfArray; + } + if (_document._order != null && order != null) { + _document._order = order; + } + } + if (_parent._child.isEmpty) { + _parent._sublayer._add(reference); + } else if (_document._order._contains(_parent._referenceHolder)) { + final int position = + _document._order._indexOf(_parent._referenceHolder); + _document._order._removeAt(position + 1); + _parent._sublayer._add(reference); + } else { + _parent._sublayer._add(reference); + } + if (_document._order._contains(_parent._referenceHolder)) { + final int position = + _document._order._indexOf(_parent._referenceHolder); + _document._order._insert(position + 1, _parent._sublayer); + } else { + if (_parent._parent != null) { + if (_parent._parent._sublayer._contains(_parent._referenceHolder)) { + int position = + _parent._parent._sublayer._indexOf(_parent._referenceHolder); + if (_parent._sublayer.count == 1) { + _parent._parent._sublayer + ._insert(position + 1, _parent._sublayer); + } + + if (_document._order._contains(_parent._parent._referenceHolder)) { + position = + _document._order._indexOf(_parent._parent._referenceHolder); + _document._order._removeAt(position + 1); + _document._order._insert(position + 1, _parent._parent._sublayer); + } + } + } + } + + _parent._child.add(layer); + + if (_parent._parentLayer.isEmpty) { + layer._parentLayer.add(_parent); + } else { + for (int i = 0; i < _parent._parentLayer.length; i++) { + if (!layer._parentLayer.contains(_parent._parentLayer[i])) { + layer._parentLayer.add(_parent._parentLayer[i]); + } + } + layer._parentLayer.add(_parent); + } + } + } + + _IPdfPrimitive _createOptionalContentViews(PdfLayer layer) { + final _PdfArray usageApplication = _PdfArray(); + _optionalContent[_DictionaryProperties.name] = _PdfString('Layers'); + _optionalContent[_DictionaryProperties.ocgOrder] = _document._order; + _optionalContent[_DictionaryProperties.ocgOn] = _document._on; + _optionalContent[_DictionaryProperties.ocgOff] = _document._off; + final _PdfArray category = _PdfArray(); + category._add(_PdfName('Print')); + final _PdfDictionary applicationDictionary = _PdfDictionary(); + applicationDictionary[_DictionaryProperties.category] = category; + applicationDictionary[_DictionaryProperties.ocg] = _document._printLayer; + applicationDictionary[_DictionaryProperties.event] = _PdfName('Print'); + usageApplication._add(_PdfReferenceHolder(applicationDictionary)); + _optionalContent[_DictionaryProperties.usageApplication] = usageApplication; + return _optionalContent; + } + + void _getDocumentLayer(PdfDocument document) { + _PdfDictionary layerDictionary; + _PdfReferenceHolder layerReference; + if (document._catalog.containsKey(_DictionaryProperties.ocProperties)) { + final _PdfDictionary ocProperties = _PdfCrossTable._dereference( + document._catalog[_DictionaryProperties.ocProperties]) + as _PdfDictionary; + if (ocProperties != null) { + if (ocProperties.containsKey(_DictionaryProperties.ocg)) { + final _PdfArray ocGroup = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.ocg]) as _PdfArray; + for (int i = 0; i < ocGroup.count; i++) { + if (ocGroup[i] is _PdfReferenceHolder) { + layerReference = ocGroup[i] as _PdfReferenceHolder; + layerDictionary = layerReference.object as _PdfDictionary; + final PdfLayer layer = PdfLayer._(); + if (layerDictionary != null && + layerDictionary.containsKey(_DictionaryProperties.name)) { + final _PdfString layerName = _PdfCrossTable._dereference( + layerDictionary[_DictionaryProperties.name]) as _PdfString; + layer.name = layerName.value; + layer._dictionary = layerDictionary; + layer._referenceHolder = layerReference; + final _IPdfPrimitive layerId = _PdfCrossTable._dereference( + layerDictionary[_DictionaryProperties.layerID]); + if (layerId != null) { + layer._layerId = layerId.toString(); + } + final _PdfDictionary usage = _PdfCrossTable._dereference( + layerDictionary[_DictionaryProperties.usage]) + as _PdfDictionary; + + if (usage != null) { + final _PdfDictionary printOption = + _PdfCrossTable._dereference( + usage[_DictionaryProperties.print]) as _PdfDictionary; + + if (printOption != null) { + layer._printOption = printOption; + } + } + } + layer._document = document; + layer._layer = layer; + // layer._parsingLayerPage(); + _layerDictionary[layerReference] = layer; + _list.add(layer); + } + } + } + _checkLayerLock(ocProperties); + _checkLayerVisible(ocProperties); + _checkParentLayer(ocProperties); + _createLayerHierarchical(ocProperties); + } + } + } + + void _checkLayerVisible(_PdfDictionary ocProperties) { + _PdfArray visible; + if (_document._catalog.containsKey(_DictionaryProperties.ocProperties)) { + final _PdfDictionary defaultView = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + + if (defaultView != null && + defaultView.containsKey(_DictionaryProperties.ocgOff)) { + visible = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOff]) as _PdfArray; + } + if (visible != null) { + for (int i = 0; i < visible.count; i++) { + final PdfLayer pdfLayer = _layerDictionary[visible[i]]; + if (pdfLayer != null) { + pdfLayer._visible = false; + if (pdfLayer._dictionary != null && + pdfLayer._dictionary + .containsKey(_DictionaryProperties.visible)) { + pdfLayer._dictionary.setProperty( + _DictionaryProperties.visible, _PdfBoolean(false)); + } + } + } + } + } + } + + void _checkParentLayer(_PdfDictionary ocProperties) { + final _PdfDictionary defaultView = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + if (defaultView != null) { + final _PdfArray array = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOrder]) as _PdfArray; + if (array != null) { + _parsingLayerOrder(array, _layerDictionary); + } + } + } + + void _checkLayerLock(_PdfDictionary ocProperties) { + _PdfArray locked; + final _PdfDictionary defaultView = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + if (defaultView != null && + defaultView.containsKey(_DictionaryProperties.ocgLock)) { + locked = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgLock]) as _PdfArray; + } + if (locked != null) { + for (int i = 0; i < locked.count; i++) { + final PdfLayer pdfLayer = _layerDictionary[locked[i]]; + if (pdfLayer != null) { + continue; + } + } + } + } + + void _createLayerHierarchical(_PdfDictionary ocProperties) { + final _PdfDictionary defaultView = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + if (defaultView != null && + defaultView.containsKey(_DictionaryProperties.ocgOrder)) { + if (_layerDictionary != null && _layerDictionary.isNotEmpty) { + _list.clear(); + _layerDictionary.forEach((_PdfReferenceHolder key, PdfLayer value) { + final PdfLayer pdflayer = value; + if (pdflayer != null) { + if (pdflayer._parent == null && !_list.contains(pdflayer)) { + _list.add(pdflayer); + } else if (pdflayer._child.isNotEmpty) { + _addChildlayer(pdflayer._parent); + } else if (pdflayer._parent != null && + pdflayer._child.isEmpty && + !pdflayer._parent.layers._list.contains(pdflayer)) { + pdflayer._parent.layers._addNestedLayer(pdflayer); + } + } + }); + } + } + } + + void _addChildlayer(PdfLayer pdflayer) { + for (int i = 0; i < pdflayer._child.length; i++) { + final PdfLayer child = pdflayer._child[i]; + if (!pdflayer.layers._list.contains(child)) { + pdflayer.layers._addNestedLayer(child); + } + } + } + + int _addNestedLayer(PdfLayer layer) { + ArgumentError.checkNotNull(layer, 'layer'); + _list.add(layer); + layer._layer = layer; + return _list.length - 1; + } + + void _parsingLayerOrder( + _PdfArray array, Map<_PdfReferenceHolder, PdfLayer> layerDictionary, + [PdfLayer parent]) { + _PdfReferenceHolder reference; + PdfLayer layer; + for (int i = 0; i < array.count; i++) { + if (array[i] is _PdfReferenceHolder) { + reference = array[i]; + layerDictionary.forEach((_PdfReferenceHolder key, PdfLayer value) { + if (identical(key.object, reference.object) || + identical(key.reference, reference.reference)) { + layer = value; + } + }); + if (layer != null) { + if (parent != null) { + parent._child.add(layer); + if (parent._parentLayer.isEmpty) { + layer._parentLayer.add(parent); + layer._parent = parent; + } else { + for (int j = 0; j < parent._parentLayer.length; j++) { + if (!layer._parentLayer.contains(parent._parentLayer[j])) { + layer._parentLayer.add(parent._parentLayer[j]); + } + } + layer._parentLayer.add(parent); + layer._parent = parent; + } + } + if (array.count > i + 1 && + _PdfCrossTable._dereference(array[i + 1]) is _PdfArray) { + i++; + final _PdfArray pdfArray = + _PdfCrossTable._dereference(array[i]) as _PdfArray; + layer._sublayer._add(pdfArray); + _parsingLayerOrder(pdfArray, layerDictionary, layer); + } + } + } else if (_PdfCrossTable._dereference(array[i]) is _PdfArray) { + final _PdfArray subarray = + _PdfCrossTable._dereference(array[i]) as _PdfArray; + if (subarray == null) { + return; + } + if (subarray[0] is _PdfString) { + parent = null; + _parsingLayerOrder(subarray, layerDictionary, parent); + } else { + parent = null; + _parsingLayerOrder(_PdfCrossTable._dereference(array[i]) as _PdfArray, + layerDictionary, parent); + } + } + } + } + + void _removeLayer(PdfLayer layer, bool isRemoveContent) { + ArgumentError.checkNotNull(layer, 'layer'); + _PdfDictionary dictionary; + if (layer != null || _document != null) { + if (_document != null) { + dictionary = _document._catalog; + if (dictionary.containsKey(_DictionaryProperties.ocProperties)) { + final _PdfDictionary ocPropertie = _PdfCrossTable._dereference( + dictionary[_DictionaryProperties.ocProperties]) as _PdfDictionary; + if (ocPropertie != null) { + final _PdfArray ocGroup = _PdfCrossTable._dereference( + ocPropertie[_DictionaryProperties.ocg]) as _PdfArray; + if (ocGroup != null) { + _removeOCProperties(ocGroup, layer._referenceHolder); + } + if (ocPropertie.containsKey(_DictionaryProperties.defaultView)) { + final _PdfDictionary defaultView = _PdfCrossTable._dereference( + ocPropertie[_DictionaryProperties.defaultView]) + as _PdfDictionary; + if (defaultView != null) { + _PdfArray _on, off; + if (defaultView.containsKey(_DictionaryProperties.ocgOrder)) { + final _PdfArray order = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOrder]) as _PdfArray; + if (order != null) { + _removeOrder(layer, order, <_PdfArray>[]); + // _removeOCProperties(order, layer._referenceHolder); + } + } + if (defaultView.containsKey(_DictionaryProperties.ocgLock)) { + final _PdfArray locked = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgLock]) as _PdfArray; + if (locked != null) { + _removeOCProperties(locked, layer._referenceHolder); + } + } + if (defaultView.containsKey(_DictionaryProperties.ocgOff)) { + off = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOff]) as _PdfArray; + } + if (defaultView.containsKey(_DictionaryProperties.ocgOn)) { + _on = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOn]) as _PdfArray; + } else if (_on == null && defaultView.containsKey('ON')) { + _on = _PdfCrossTable._dereference(defaultView['ON']) + as _PdfArray; + } + if (defaultView + .containsKey(_DictionaryProperties.usageApplication)) { + final _PdfArray usage = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.usageApplication]) + as _PdfArray; + if (usage != null) { + _removeOCProperties(usage, layer._referenceHolder); + } + } + _removeVisible(layer, _on, off); + } + } + } + } + } + if (isRemoveContent) { + _removeLayerContent(layer); + } + } + } + + void _removeVisible(PdfLayer layer, _PdfArray _on, _PdfArray off) { + if (layer.visible) { + if (_on != null) { + _removeOCProperties(_on, layer._referenceHolder); + } + } else { + if (off != null) { + _removeOCProperties(off, layer._referenceHolder); + } + } + } + + void _removeOrder( + PdfLayer layer, _PdfArray order, List<_PdfArray> arrayList) { + bool isRemoveOrder = false; + if (order != null) { + for (int i = 0; i < order.count; i++) { + if (order[i] is _PdfReferenceHolder) { + final _PdfReferenceHolder holder = order[i]; + if (identical(holder.object, layer._referenceHolder.object) || + identical(holder.reference, layer._referenceHolder.reference)) { + if (i != order.count - 1) { + if (order[i + 1] is _PdfArray) { + order._removeAt(i); + order._removeAt(i); + isRemoveOrder = true; + break; + } else { + order._removeAt(i); + isRemoveOrder = true; + break; + } + } else { + order._removeAt(i); + isRemoveOrder = true; + break; + } + } + } else if (order[i] is _PdfArray) { + arrayList.add(order[i] as _PdfArray); + } + } + } + if (!isRemoveOrder) { + if (arrayList != null) { + for (int i = 0; i < arrayList.length; i++) { + order = arrayList[i]; + arrayList.removeAt(i); + i = i - 1; + _removeOrder(layer, order, arrayList); + } + } + } + } + + void _removeOCProperties( + _PdfArray content, _PdfReferenceHolder referenceHolder) { + bool isChange = false; + for (int i = 0; i < content.count; i++) { + if (content._elements[i] is _PdfReferenceHolder) { + final _PdfReferenceHolder holder = content._elements[i]; + if (holder.reference != null && referenceHolder.reference != null) { + if (holder.reference._objNum == referenceHolder.reference._objNum) { + content._elements.removeAt(i); + isChange = true; + i--; + } + } else if (identical(holder, referenceHolder)) { + content._elements.removeAt(i); + isChange = true; + i--; + } else if (identical(holder._object, referenceHolder._object)) { + content._elements.removeAt(i); + isChange = true; + i--; + } + } else if (content._elements[i] is _PdfArray) { + _removeOCProperties(content._elements[i], referenceHolder); + } + } + if (isChange) { + content._isChanged = true; + } + } + + void _removeLayerContent(PdfLayer layer) { + _PdfDictionary properties; + bool isSkip = false; + _PdfDictionary xObject; + if (!layer._pageParsed) { + layer._parsingLayerPage(); + _removeLayerContent(layer); + return; + } + if (layer._pages.isNotEmpty) { + for (int i = 0; i < layer._pages.length; i++) { + final _PdfDictionary resource = _PdfCrossTable._dereference( + layer._pages[i]._dictionary[_DictionaryProperties.resources]) + as _PdfDictionary; + if (resource != null) { + properties = _PdfCrossTable._dereference( + resource[_DictionaryProperties.properties]) as _PdfDictionary; + xObject = _PdfCrossTable._dereference( + resource[_DictionaryProperties.xObject]) as _PdfDictionary; + if (properties != null) { + if (properties.containsKey(layer._layerId)) { + properties.remove(layer._layerId); + } + } + if (xObject != null && layer._xobject.isNotEmpty) { + for (final _PdfName key in xObject._items.keys) { + if (layer._xobject.contains(key._name)) { + xObject.remove(key); + } + if (xObject._items.isEmpty) { + break; + } + } + } + final _PdfArray content = 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 (layer._pages[i]._isLoadedPage) { + pageContent._decompress(); + } + stream = pageContent._dataStream; + final _ContentParser parser = _ContentParser(stream); + final _PdfRecordCollection recordCollection = parser._readContent(); + for (int j = 0; + j < recordCollection._recordCollection.length; + j++) { + final String mOperator = + recordCollection._recordCollection[j]._operatorName; + if (mOperator == 'BMC' || + mOperator == 'EMC' || + mOperator == 'BDC') { + _processBeginMarkContent(layer, mOperator, + recordCollection._recordCollection[j]._operands, data); + isSkip = true; + } + if (mOperator == _Operators.paintXObject) { + if (layer._xobject.contains( + recordCollection._recordCollection[j]._operands[0])) { + isSkip = true; + } + } + if (mOperator == 'RG' || + mOperator == _Operators.saveState || + mOperator == _Operators.restoreState || + mOperator == _Operators.setLineWidth || + mOperator == _Operators.setLineCapStyle || + mOperator == _Operators.setLineJoinStyle || + mOperator == _Operators.setMiterLimit || + mOperator == _Operators.setDashPattern || + mOperator == _Operators.setGraphicsState || + mOperator == _Operators.currentMatrix || + mOperator == _Operators.selectColorSpaceForNonStroking || + mOperator == _Operators.selectColorSpaceForStroking) { + if (!isSkip) { + _streamWrite(recordCollection._recordCollection[j]._operands, + mOperator, false, data); + } + } else if (!isSkip) { + _streamWrite(recordCollection._recordCollection[j]._operands, + mOperator, true, data); + } + isSkip = false; + } + if (data._dataStream.isNotEmpty) { + pageContent.clear(); + pageContent._dataStream.clear(); + pageContent._write(data._dataStream); + } else { + pageContent.clear(); + } + } + } + } + } + } + + void _processBeginMarkContent(PdfLayer parser, String mOperator, + List operands, _PdfStream data) { + if ('BDC' == mOperator) { + String operand; + if (operands.length > 1 && ((operands[0]) == '/OC')) { + operand = operands[1].substring(1); + } + if (_bdcCount > 0) { + _bdcCount++; + return; + } + if (operand != null && (operand == parser._layerId)) { + _bdcCount++; + } + } + _streamWrite(operands, mOperator, true, data); + if (('EMC' == mOperator) && _bdcCount > 0) { + _bdcCount--; + } + } + + void _streamWrite( + List operands, String mOperator, bool skip, _PdfStream data) { + _PdfString pdfString; + if (skip && _bdcCount > 0) { + return; + } + if (operands != null) { + for (final String operand in operands) { + pdfString = _PdfString(operand); + data._write(pdfString.data); + data._write(_Operators.whiteSpace); + } + } + pdfString = _PdfString(mOperator); + data._write(pdfString.data); + data._write(_Operators.newLine); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page.dart index 44b0fccf6..77e887991 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 @@ -40,6 +40,7 @@ class PdfPage implements _IPdfWrapper { final _PdfArray _annotsReference = _PdfArray(); bool _isTextExtraction; bool _graphicStateUpdated; + bool _isDefaultGraphics = false; /// Raises before the page saves. Function _beginSave; @@ -70,7 +71,14 @@ class PdfPage implements _IPdfWrapper { } } - Offset get _origin => _section.pageSettings._origin.offset; + Offset get _origin { + if (_section != null) { + return _section.pageSettings._origin.offset; + } else { + return Offset.zero; + } + } + PdfDocument get _document { if (_isLoadedPage) { return _pdfDocument; @@ -109,7 +117,10 @@ class PdfPage implements _IPdfWrapper { } /// Gets the graphics of the [DefaultLayer]. - PdfGraphics get graphics => defaultLayer.graphics; + PdfGraphics get graphics { + _isDefaultGraphics = true; + return defaultLayer.graphics; + } /// Gets the collection of the page's layers (Read only). PdfPageLayerCollection get layers { @@ -462,6 +473,20 @@ class PdfPage implements _IPdfWrapper { _annotations = PdfAnnotationCollection._(this); } + /// Creates a template from the page content. + PdfTemplate createTemplate() { + return _getContent(); + } + + PdfTemplate _getContent() { + final List combinedData = layers._combineContent(false); + final _PdfDictionary resources = _PdfCrossTable._dereference( + _dictionary[_DictionaryProperties.resources]) as _PdfDictionary; + final PdfTemplate template = + PdfTemplate._(_origin, size, combinedData, resources, _isLoadedPage); + return template; + } + //_IPdfWrapper elements @override _IPdfPrimitive get _element => _dictionary; 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 92ec1226c..295924577 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 @@ -20,7 +20,7 @@ class PdfPageLayer implements _IPdfWrapper { _PdfStream _content; PdfPage _page; bool _clipPageTemplates; - //ignore:unused_field + String _name; PdfGraphics _graphics; //ignore:unused_field @@ -28,6 +28,12 @@ class PdfPageLayer implements _IPdfWrapper { PdfGraphicsState _graphicsState; bool _isEndState = false; bool _isSaved = false; + _PdfDictionary _dictionary; + bool _visible = true; + String _layerID; + _PdfDictionary _printOption; + _PdfDictionary _usage; + _PdfReferenceHolder _referenceHolder; //Properties /// Gets parent page of the layer. @@ -42,6 +48,36 @@ class PdfPageLayer implements _IPdfWrapper { return _graphics; } + /// Gets the name of the layer + String get name { + return _name; + } + + /// Sets the name of the layer + set name(String value) { + _name = value; + _layerID ??= 'OCG_' + _PdfResources._globallyUniqueIdentifier; + } + + /// Gets the visibility of the page layer. + bool get visible { + if (_dictionary != null && + _dictionary.containsKey(_DictionaryProperties.visible)) { + _visible = + (_dictionary[_DictionaryProperties.visible] as _PdfBoolean).value; + } + return _visible; + } + + /// Sets the visibility of the page layer. + set visible(bool value) { + _visible = value; + if (_dictionary != null) { + _dictionary[_DictionaryProperties.visible] = _PdfBoolean(value); + } + _setVisibility(_visible); + } + //Implementation void _initialize(PdfPage pdfPage, bool clipPageTemplates) { if (pdfPage != null) { @@ -51,6 +87,7 @@ class PdfPageLayer implements _IPdfWrapper { } _clipPageTemplates = clipPageTemplates; _content = _PdfStream(); + _dictionary = _PdfDictionary(); } void _initializeGraphics(PdfPage page) { @@ -68,6 +105,7 @@ class PdfPageLayer implements _IPdfWrapper { double ury = 0; final _PdfArray mediaBox = page._dictionary._getValue( _DictionaryProperties.mediaBox, _DictionaryProperties.parent); + final _PdfReferenceHolder referenceHolder = _PdfReferenceHolder(this); if (mediaBox != null) { // Lower Left X co-ordinate Value. llx = (mediaBox[0] as _PdfNumber).value.toDouble(); @@ -92,9 +130,19 @@ class PdfPageLayer implements _IPdfWrapper { final Size pageSize = Size([cropX, cropRX].reduce(max), [cropY, cropRY].reduce(max)); _graphics = PdfGraphics._(pageSize, resources, _content); + if (!page._contents._contains(referenceHolder) && + !page._isDefaultGraphics && + !_isContainsPageContent(page._contents, referenceHolder)) { + page._contents._add(referenceHolder); + } } else { _graphics = PdfGraphics._(page.size, resources, _content); _graphics._cropBox = cropBox; + if (!page._contents._contains(referenceHolder) && + !page._isDefaultGraphics && + !_isContainsPageContent(page._contents, referenceHolder)) { + page._contents._add(referenceHolder); + } } } else if ((llx < 0 || lly < 0 || urx < 0 || ury < 0) && (lly.abs().floor() == page.size.height.abs().floor()) && @@ -114,9 +162,19 @@ class PdfPageLayer implements _IPdfWrapper { } pageSize = Size([llx, urx].reduce(max), [lly, ury].reduce(max)); _graphics = PdfGraphics._(pageSize, resources, _content); + if (!page._contents._contains(referenceHolder) && + !page._isDefaultGraphics && + !_isContainsPageContent(page._contents, referenceHolder)) { + page._contents._add(referenceHolder); + } } } else { _graphics = PdfGraphics._(page.size, resources, _content); + if (!page._contents._contains(referenceHolder) && + !page._isDefaultGraphics && + !_isContainsPageContent(page._contents, referenceHolder)) { + page._contents._add(referenceHolder); + } } if (isPageHasMediaBox) { @@ -132,6 +190,10 @@ class PdfPageLayer implements _IPdfWrapper { _content._beginSave = _beginSaveContent; } _graphicsState = _graphics.save(); + if (name != null && name.isNotEmpty) { + _graphics._streamWriter._write('/OC /' + _layerID + ' BDC\n'); + _isEndState = true; + } _graphics._initializeCoordinates(); if (_graphics._hasTransparencyBrush) { _graphics._setTransparencyGroup(page); @@ -223,6 +285,87 @@ class PdfPageLayer implements _IPdfWrapper { _isSaved = true; } + void _setVisibility(bool value) { + _PdfDictionary oCProperties; + if (_page._document._catalog + .containsKey(_DictionaryProperties.ocProperties)) { + oCProperties = _PdfCrossTable._dereference( + _page._document._catalog[_DictionaryProperties.ocProperties]) + as _PdfDictionary; + } + if (oCProperties != null) { + final _PdfDictionary defaultView = + oCProperties[_DictionaryProperties.defaultView] as _PdfDictionary; + if (defaultView != null) { + _PdfArray ocgON = defaultView[_DictionaryProperties.ocgOn] as _PdfArray; + _PdfArray ocgOFF = + defaultView[_DictionaryProperties.ocgOff] as _PdfArray; + if (_referenceHolder != null) { + if (value == false) { + if (ocgON != null) { + _removeContent(ocgON, _referenceHolder); + } + if (ocgOFF == null) { + ocgOFF = _PdfArray(); + defaultView._items[_PdfName(_DictionaryProperties.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(_DictionaryProperties.ocgOn)] = ocgON; + } + ocgON._insert(ocgON.count, _referenceHolder); + } + } + } + } + } + + bool _isContainsPageContent( + _PdfArray content, _PdfReferenceHolder referenceHolder) { + for (int i = 0; i < content.count; i++) { + if (content._elements[i] is _PdfReferenceHolder) { + final _PdfReferenceHolder holder = content._elements[i]; + if (holder.reference != null && referenceHolder.reference != null) { + if (holder.reference._objNum == referenceHolder.reference._objNum) { + return true; + } + } else { + if (identical(holder, referenceHolder)) { + return true; + } else if (identical(holder._object, referenceHolder._object)) { + return true; + } + } + } + } + return false; + } + + void _removeContent(_PdfArray content, _PdfReferenceHolder referenceHolder) { + bool flag = false; + for (int i = 0; i < content.count; i++) { + if (content._elements[i] is _PdfReferenceHolder) { + final _PdfReferenceHolder holder = content._elements[i]; + if (holder.reference != null && referenceHolder.reference != null) { + if (holder.reference._objNum == referenceHolder.reference._objNum) { + content._elements.removeAt(i); + flag = true; + i--; + } + } + } + } + if (flag) { + content._isChanged = true; + } + } + //_IPdfWrapper elements @override _IPdfPrimitive get _element => _content; 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 136931eeb..5b21fdecb 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 @@ -8,12 +8,17 @@ class PdfPageLayerCollection extends PdfObjectCollection { /// [PdfPageLayerCollection] class with PDF page. PdfPageLayerCollection(PdfPage page) : super() { ArgumentError.checkNotNull(page, 'page'); + _optionalContent = _PdfDictionary(); + _subLayer = false; _page = page; _parseLayers(page); } //Fields PdfPage _page; + bool _subLayer; + _PdfDictionary _optionalContent; + int _bdcCount = 0; //Properties /// Gets [PdfPageLayer] by its index from [PdfPageLayerCollection]. @@ -23,10 +28,16 @@ class PdfPageLayerCollection extends PdfObjectCollection { } //Public methods - /// Creates a new and adds it to the end of the collection. - PdfPageLayer add() { + /// Creates a new [PdfPageLayer] and adds it to the end of the collection. + PdfPageLayer add({String name, bool visible}) { final PdfPageLayer layer = PdfPageLayer(_page); - layer._name = ''; + if (name != null) { + layer.name = name; + layer._layerID ??= 'OCG_' + _PdfResources._globallyUniqueIdentifier; + } + if (visible != null) { + layer.visible = visible; + } addLayer(layer); return layer; } @@ -40,6 +51,19 @@ class PdfPageLayerCollection extends PdfObjectCollection { _list.add(layer); final int listIndex = count - 1; _page._contents._add(_PdfReferenceHolder(layer)); + if (layer._layerID != null) { + if (_page._isLoadedPage) { + _createLayerLoadedPage(layer); + } else { + final _PdfDictionary ocProperties = _PdfDictionary(); + ocProperties[_DictionaryProperties.ocg] = + _createOptionContentDictionary(layer); + ocProperties[_DictionaryProperties.defaultView] = + _createOptionalContentViews(layer); + _page._document._catalog[_DictionaryProperties.ocProperties] = + ocProperties; + } + } return listIndex; } @@ -55,6 +79,45 @@ class PdfPageLayerCollection extends PdfObjectCollection { ArgumentError.checkNotNull(page, 'page'); if (!page._isTextExtraction) { final _PdfArray contents = page._contents; + + final _PdfDictionary resource = _page._getResources(); + _PdfDictionary ocProperties; + _PdfDictionary propertie; + PdfPage pdfLoaded; + final Map<_PdfReferenceHolder, PdfPageLayer> pageLayerCollection = + <_PdfReferenceHolder, PdfPageLayer>{}; + if (page._isLoadedPage) { + pdfLoaded = page; + } + if (pdfLoaded != null) { + propertie = _PdfCrossTable._dereference( + resource[_DictionaryProperties.properties]) as _PdfDictionary; + if (pdfLoaded._document != null) { + ocProperties = _PdfCrossTable._dereference(pdfLoaded._document + ._catalog[_DictionaryProperties.ocProperties]) as _PdfDictionary; + } + } + + if (ocProperties != null && (propertie != null)) { + _PdfDictionary layerDictionary; + _PdfReferenceHolder layerReferenceHolder; + if (propertie != null && (propertie != null)) { + propertie._items.forEach((_PdfName key, _IPdfPrimitive value) { + layerReferenceHolder = value as _PdfReferenceHolder; + layerDictionary = + _PdfCrossTable._dereference(value) as _PdfDictionary; + if ((layerDictionary != null && layerReferenceHolder != null) || + layerDictionary.containsKey(_DictionaryProperties.ocg)) { + _addLayer(page, layerDictionary, layerReferenceHolder, key._name, + pageLayerCollection, false); + } + }); + } + } + if (ocProperties != null && pageLayerCollection.isNotEmpty) { + _checkVisible(ocProperties, pageLayerCollection); + } + final _PdfStream saveStream = _PdfStream(); final _PdfStream restoreStream = _PdfStream(); const int saveState = 113; @@ -75,7 +138,7 @@ class PdfPageLayerCollection extends PdfObjectCollection { final bool decompress = _page._isLoadedPage; List combinedData; final List end = [13, 10]; - if (_page._isLoadedPage && _page._contents.count != count + 2) { + if (_page._isLoadedPage && (_page._contents.count != count + 2)) { combinedData = _combineProcess(_page, decompress, end, skipSave); } return combinedData; @@ -110,4 +173,511 @@ class PdfPageLayerCollection extends PdfObjectCollection { } return data; } + + void _createLayerLoadedPage(PdfPageLayer layer) { + final _PdfDictionary ocProperties = _PdfDictionary(); + final _IPdfPrimitive ocgroups = _createOptionContentDictionary(layer); + bool isPresent = false; + if (_page != null && + _page._document != null && + _page._document._catalog != null && + _page._document._catalog + .containsKey(_DictionaryProperties.ocProperties)) { + final _PdfDictionary ocDictionary = _PdfCrossTable._dereference( + _page._document._catalog[_DictionaryProperties.ocProperties]) + as _PdfDictionary; + if (ocDictionary != null && + ocDictionary.containsKey(_DictionaryProperties.ocg)) { + final _PdfArray ocgsList = + _PdfCrossTable._dereference(ocDictionary[_DictionaryProperties.ocg]) + as _PdfArray; + if (ocgsList != null) { + isPresent = true; + if (!ocgsList._contains(layer._referenceHolder)) { + ocgsList._insert(ocgsList.count, layer._referenceHolder); + } + } + if (ocDictionary.containsKey(_DictionaryProperties.defaultView)) { + final _PdfDictionary defaultView = + ocDictionary[_DictionaryProperties.defaultView] as _PdfDictionary; + if (defaultView != null) { + _PdfArray _on = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOn]) as _PdfArray; + final _PdfArray order = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOrder]) as _PdfArray; + _PdfArray off = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOff]) as _PdfArray; + final _PdfArray usage = _PdfCrossTable._dereference( + (defaultView[_DictionaryProperties.usageApplication])) + as _PdfArray; + + if (_on == null) { + _on = _PdfArray(); + defaultView[_DictionaryProperties.ocgOn] = _on; + } + + if (!layer.visible && off == null) { + off = _PdfArray(); + defaultView[_DictionaryProperties.ocgOff] = off; + } + + if (order != null && !order._contains(layer._referenceHolder)) { + order._insert(order.count, layer._referenceHolder); + } + + if (layer.visible && !_on._contains(layer._referenceHolder)) { + _on._insert(_on.count, layer._referenceHolder); + } + if (!layer.visible && + off != null && + !off._contains(layer._referenceHolder)) { + off._insert(off.count, layer._referenceHolder); + } + + if (usage != null && usage.count > 0) { + final _PdfDictionary asDictionary = + _PdfCrossTable._dereference(usage[0]) as _PdfDictionary; + if (asDictionary != null && + asDictionary.containsKey(_DictionaryProperties.ocg)) { + final _PdfArray usageOcGroup = _PdfCrossTable._dereference( + asDictionary[_DictionaryProperties.ocg]) as _PdfArray; + if (usageOcGroup != null && + !usageOcGroup._contains(layer._referenceHolder)) { + usageOcGroup._insert( + usageOcGroup.count, layer._referenceHolder); + } + } + } + } + } + } + } + if (!isPresent && + _page != null && + _page._document != null && + _page._document._catalog != null) { + ocProperties[_DictionaryProperties.ocg] = ocgroups; + ocProperties[_DictionaryProperties.defaultView] = + _createOptionalContentViews(layer); + _page._document._catalog + .setProperty(_DictionaryProperties.ocProperties, ocProperties); + } + } + + _IPdfPrimitive _createOptionContentDictionary(PdfPageLayer layer) { + final _PdfDictionary optionalContent = _PdfDictionary(); + optionalContent[_DictionaryProperties.name] = _PdfString(layer.name); + optionalContent[_DictionaryProperties.type] = _PdfName('OCG'); + optionalContent[_DictionaryProperties.layerID] = _PdfName(layer._layerID); + optionalContent[_DictionaryProperties.visible] = _PdfBoolean(layer.visible); + layer._usage = _setPrintOption(layer); + optionalContent[_DictionaryProperties.usage] = + _PdfReferenceHolder(layer._usage); + _page._document._printLayer._add(_PdfReferenceHolder(optionalContent)); + final _PdfReferenceHolder reference = _PdfReferenceHolder(optionalContent); + _page._document._primitive._add(reference); + layer._dictionary = optionalContent; + layer._referenceHolder = reference; + if (!_subLayer) { + _page._document._order._add(reference); + _page._document._orderPosition++; + } + if (layer.visible) { + _page._document._on._add(reference); + _page._document._onPosition++; + } else { + _page._document._off._add(reference); + _page._document._offPosition++; + } + _page._document._position++; + final _PdfResources resource = _page._getResources(); + if (resource != null && + resource.containsKey(_DictionaryProperties.properties) && + _page._isLoadedPage) { + final _PdfDictionary dic = + resource[_DictionaryProperties.properties] as _PdfDictionary; + if (dic != null) { + dic[layer._layerID] = reference; + } else { + resource._properties[layer._layerID] = reference; + resource[_DictionaryProperties.properties] = resource._properties; + } + } else { + resource._properties[layer._layerID] = reference; + resource[_DictionaryProperties.properties] = resource._properties; + } + return _page._document._primitive; + } + + _PdfDictionary _setPrintOption(PdfPageLayer layer) { + final _PdfDictionary _usage = _PdfDictionary(); + layer._printOption = _PdfDictionary(); + layer._printOption[_DictionaryProperties.subtype] = _PdfName('Print'); + _usage[_DictionaryProperties.print] = + _PdfReferenceHolder(layer._printOption); + return _usage; + } + + _IPdfPrimitive _createOptionalContentViews(PdfPageLayer layer) { + final _PdfArray usageApplication = _PdfArray(); + _optionalContent[_DictionaryProperties.name] = _PdfString('Layers'); + _optionalContent[_DictionaryProperties.ocgOrder] = _page._document._order; + _optionalContent[_DictionaryProperties.ocgOn] = _page._document._on; + _optionalContent[_DictionaryProperties.ocgOff] = _page._document._off; + final _PdfArray category = _PdfArray(); + category._add(_PdfName('Print')); + final _PdfDictionary applicationDictionary = _PdfDictionary(); + applicationDictionary[_DictionaryProperties.category] = category; + applicationDictionary[_DictionaryProperties.ocg] = + _page._document._printLayer; + applicationDictionary[_DictionaryProperties.event] = _PdfName('Print'); + usageApplication._add(_PdfReferenceHolder(applicationDictionary)); + if (_page._document._conformanceLevel != PdfConformanceLevel.a2b && + _page._document._conformanceLevel != PdfConformanceLevel.a3b) { + _optionalContent[_DictionaryProperties.usageApplication] = + usageApplication; + } + return _optionalContent; + } + + void _addLayer( + PdfPage page, + _PdfDictionary dictionary, + _PdfReferenceHolder reference, + String key, + Map<_PdfReferenceHolder, PdfPageLayer> pageLayerCollection, + bool isResourceLayer) { + final PdfPageLayer layer = PdfPageLayer(page); + _list.add(layer); + if (!pageLayerCollection.containsKey(reference)) { + pageLayerCollection[reference] = layer; + } + layer._dictionary = dictionary; + layer._referenceHolder = reference; + layer._layerID = key; + if (dictionary.containsKey(_DictionaryProperties.name)) { + final _PdfString layerName = + _PdfCrossTable._dereference(dictionary[_DictionaryProperties.name]) + as _PdfString; + if (layerName != null) { + layer._name = layerName.value; + } + } + } + + void _checkVisible(_PdfDictionary ocproperties, + Map<_PdfReferenceHolder, PdfPageLayer> layerDictionary) { + if (ocproperties != null) { + final _PdfDictionary defaultView = _PdfCrossTable._dereference( + ocproperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + if (defaultView != null) { + final _PdfArray visible = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOff]) as _PdfArray; + if (visible != null && layerDictionary.isNotEmpty) { + for (int i = 0; i < visible.count; i++) { + if (layerDictionary + .containsKey(visible[i] as _PdfReferenceHolder)) { + final PdfPageLayer pdfLayer = + layerDictionary[visible[i] as _PdfReferenceHolder]; + if (pdfLayer != null) { + pdfLayer._visible = false; + if (pdfLayer._dictionary != null && + pdfLayer._dictionary + .containsKey(_DictionaryProperties.visible)) { + pdfLayer._dictionary.setProperty( + _DictionaryProperties.visible, _PdfBoolean(false)); + } + } + } + } + } + } + } + } + + /// Removes layer from the collection. + void remove({PdfPageLayer layer, String name}) { + if (layer == null && name == null) { + ArgumentError.value('layer or layerName required'); + } + if (layer != null) { + _removeLayer(layer); + _list.remove(layer); + } else { + for (int i = 0; i < _list.length; i++) { + final PdfPageLayer layer = _list[i] as PdfPageLayer; + if (layer.name == name) { + _removeLayer(layer); + _list.remove(layer); + break; + } + } + } + } + + /// Removes layer by its index from collections + 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'); + } + final PdfPageLayer layer = this[index]; + if (layer != null) { + _removeLayer(layer); + _list.removeAt(index); + } + } + + /// Clears layers from the [PdfPageLayerCollection]. + void clear() { + for (final PdfPageLayer layer in _page.layers._list) { + _removeLayer(layer); + } + _list.clear(); + } + + void _removeLayer(PdfPageLayer layer) { + ArgumentError.checkNotNull(layer, 'Layer'); + _PdfDictionary ocProperties; + if (_page != null) { + _removeLayerContent(layer); + final _PdfDictionary resource = _PdfCrossTable._dereference( + _page._dictionary[_DictionaryProperties.resources]) as _PdfDictionary; + if (resource != null) { + final _PdfDictionary properties = _PdfCrossTable._dereference( + resource[_DictionaryProperties.properties]) as _PdfDictionary; + if (properties != null && + layer._layerID != null && + properties.containsKey(layer._layerID)) { + properties.remove(layer._layerID); + } + } + final PdfPage page = _page; + if (page != null) { + if (page._document != null && + page._document._catalog + .containsKey(_DictionaryProperties.ocProperties)) { + ocProperties = _PdfCrossTable._dereference( + page._document._catalog[_DictionaryProperties.ocProperties]) + as _PdfDictionary; + } + } + if (ocProperties != null) { + final _PdfArray ocGroup = + _PdfCrossTable._dereference(ocProperties[_DictionaryProperties.ocg]) + as _PdfArray; + if (ocGroup != null) { + _removeContent(ocGroup, layer._referenceHolder); + } + final _PdfDictionary defaultView = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + if (defaultView != null) { + final _PdfArray _on = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOn]) as _PdfArray; + final _PdfArray order = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOrder]) as _PdfArray; + final _PdfArray off = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOff]) as _PdfArray; + final _PdfArray usage = _PdfCrossTable._dereference( + (defaultView[_DictionaryProperties.usageApplication])) + as _PdfArray; + + if (usage != null && usage.count > 0) { + for (int i = 0; i < usage.count; i++) { + final _PdfDictionary usageDictionary = + _PdfCrossTable._dereference(usage[i]) as _PdfDictionary; + if (usageDictionary != null && + usageDictionary.containsKey(_DictionaryProperties.ocg)) { + final _PdfArray usageOcGroup = + usageDictionary[_DictionaryProperties.ocg] as _PdfArray; + if (usageOcGroup != null) { + _removeContent(usageOcGroup, layer._referenceHolder); + } + } + } + } + if (order != null) { + _removeContent(order, layer._referenceHolder); + } + if (layer.visible && _on != null) { + _removeContent(_on, layer._referenceHolder); + } else if (off != null) { + _removeContent(off, layer._referenceHolder); + } + } + } + } + } + + void _removeContent(_PdfArray content, _PdfReferenceHolder referenceHolder) { + bool flag = false; + for (int i = 0; i < content.count; i++) { + if (content._elements[i] is _PdfReferenceHolder) { + if ((content._elements[i] as _PdfReferenceHolder).reference != null && + referenceHolder.reference != null) { + if ((content._elements[i] as _PdfReferenceHolder).reference._objNum == + referenceHolder.reference._objNum) { + content._elements.removeAt(i); + flag = true; + i--; + } + } else if (identical(content._elements[i], referenceHolder)) { + content._elements.removeAt(i); + flag = true; + i--; + } else if (identical( + (content._elements[i] as _PdfReferenceHolder)._object, + referenceHolder._object)) { + content._elements.removeAt(i); + flag = true; + i--; + } + } else if (content._elements[i] is _PdfArray) { + _removeContent(content._elements[i], referenceHolder); + } + } + if (flag) { + content._isChanged = true; + } + } + + void _removeLayerContent(PdfPageLayer layer) { + bool isSkip = false; + for (int m = 0; m < _page._contents.count; m++) { + bool isNewContentStream = false; + bool removePageContent = false; + List stream = []; + final _PdfReferenceHolder pdfReference = + _page._contents[m] as _PdfReferenceHolder; + if (pdfReference != null && pdfReference.reference == null) { + isNewContentStream = true; + } + final _PdfStream pageContent = + _PdfCrossTable._dereference(_page._contents[m]) as _PdfStream; + final _PdfStream data = _PdfStream(); + if (_page._isLoadedPage) { + pageContent._decompress(); + } + stream = pageContent._dataStream; + final _ContentParser parser = _ContentParser(stream); + final _PdfRecordCollection recordCollection = parser._readContent(); + for (int j = 0; j < recordCollection._recordCollection.length; j++) { + final String mOperator = + recordCollection._recordCollection[j]._operatorName; + if (mOperator == 'BMC' || mOperator == 'EMC' || mOperator == 'BDC') { + final Map returnedValue = _processBeginMarkContent( + layer, + mOperator, + recordCollection._recordCollection[j]._operands, + data, + isNewContentStream, + removePageContent); + removePageContent = returnedValue['removePageContent']; + isSkip = true; + if (removePageContent) { + break; + } + } + String id; + if (recordCollection._recordCollection[j]._operands.isNotEmpty && + recordCollection._recordCollection[j]._operands[0] + .startsWith('/')) { + id = recordCollection._recordCollection[j]._operands[0].substring(1); + } + if (mOperator == _Operators.paintXObject && (id == layer._layerID)) { + isSkip = true; + } + if (mOperator == 'RG' || + mOperator == _Operators.saveState || + mOperator == _Operators.restoreState || + mOperator == _Operators.setLineWidth || + mOperator == _Operators.setLineCapStyle || + mOperator == _Operators.setLineJoinStyle || + mOperator == _Operators.setMiterLimit || + mOperator == _Operators.setDashPattern || + mOperator == _Operators.setGraphicsState || + mOperator == _Operators.currentMatrix || + mOperator == _Operators.selectColorSpaceForNonStroking || + mOperator == _Operators.selectColorSpaceForStroking) { + if (!isSkip) { + _streamWrite(recordCollection._recordCollection[j]._operands, + mOperator, false, data); + } + } else if (!isSkip) { + _streamWrite(recordCollection._recordCollection[j]._operands, + mOperator, true, data); + } + isSkip = false; + } + if (data._dataStream.isNotEmpty && !removePageContent) { + pageContent.clear(); + pageContent._dataStream.clear(); + pageContent._write(data._dataStream); + } else { + pageContent.clear(); + } + if (removePageContent) { + _removeContent(_page._contents, layer._referenceHolder); + if (layer._graphics != null && layer._graphics._streamWriter != null) { + final _PdfStream lcontent = layer._graphics._streamWriter._stream; + if (lcontent != null) { + _removeContent(_page._contents, _PdfReferenceHolder(lcontent)); + } + } + } + } + } + + Map _processBeginMarkContent( + PdfPageLayer parser, + String mOperator, + List operands, + _PdfStream data, + bool isNewContentStream, + bool removePageContent) { + removePageContent = false; + if ('BDC' == mOperator) { + String operand; + if (operands.length > 1 && ((operands[0]) == '/OC')) { + operand = operands[1].substring(1); + } + if (_bdcCount > 0) { + _bdcCount++; + return {'removePageContent': removePageContent}; + } + if (operand != null && + (operand == parser._layerID) && + !isNewContentStream) { + _bdcCount++; + } else if (operand != null && + (operand == parser._layerID) && + isNewContentStream) { + removePageContent = true; + } + } + _streamWrite(operands, mOperator, true, data); + + if (('EMC' == mOperator) && _bdcCount > 0) { + _bdcCount--; + } + return {'removePageContent': removePageContent}; + } + + void _streamWrite( + List operands, String mOperator, bool skip, _PdfStream data) { + _PdfString pdfString; + if (skip && _bdcCount > 0) { + return; + } + if (operands != null) { + for (final String operand in operands) { + pdfString = _PdfString(operand); + data._write(pdfString.data); + data._write(_Operators.whiteSpace); + } + } + pdfString = _PdfString(mOperator); + data._write(pdfString.data); + data._write(_Operators.newLine); + } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_template_element.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_template_element.dart index 3bdcec4f7..e0e0e8604 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 @@ -150,6 +150,7 @@ class PdfPageTemplateElement { ArgumentError.checkNotNull(document, 'document'); final PdfPage page = layer.page; final Rect bounds = _calculateBounds(page, document); + layer._page._isDefaultGraphics = true; layer.graphics.drawPdfTemplate(_template, bounds.topLeft, bounds.size); } 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 new file mode 100644 index 000000000..a96a00a9a --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment.dart @@ -0,0 +1,83 @@ +part of pdf; + +/// Represents the attachments of the PDF document. +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) { + _updateValues(description, mimeType); + } + + /// 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)) { + _updateValues(description, mimeType); + } + + //Properties. + /// Gets the description. + @override + String get description => super.description; + + /// Sets the description. + @override + set description(String value) => super.description = value; + + /// Gets the file name. + @override + String get fileName => _embeddedFile.fileName; + + /// Sets the file name. + @override + set fileName(String value) => _embeddedFile.fileName = value; + + /// Gets the data. + List get data => _embeddedFile.data; + + /// Sets the data. + set data(List value) => _embeddedFile.data = value; + + /// Gets the MIME type of the embedded file. + String get mimeType => _embeddedFile.mimeType; + + /// Sets the MIME type of the embedded file. + set mimeType(String value) => _embeddedFile.mimeType = value; + + /// Gets creation date. + DateTime get creationDate => _embeddedFile._params._creationDate; + + /// Sets creation date. + set creationDate(DateTime value) => + _embeddedFile._params._creationDate = value; + + /// Gets modification date. + DateTime get modificationDate => _embeddedFile._params._modificationDate; + + /// Sets modification date. + set modificationDate(DateTime value) => + _embeddedFile._params._modificationDate = value; + + /// Get the file relationship. + PdfAttachmentRelationship get relationship => _relationship; + + /// Set the file relationship. + set relationship(PdfAttachmentRelationship value) { + _relationship = value; + _dictionary.setProperty(_DictionaryProperties.afRelationship, + _PdfName(_getEnumName(_relationship))); + } + + void _updateValues(String desc, String mime) { + if (mime != null) { + mimeType = mime; + } + if (desc != null) { + description = desc; + } + } +} 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 new file mode 100644 index 000000000..d061205a2 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment_collection.dart @@ -0,0 +1,372 @@ +part of pdf; + +/// Represents a collection of the attachment objects. +class PdfAttachmentCollection extends PdfObjectCollection + implements _IPdfWrapper { + //Constructors. + /// Initializes a new instance of the [PdfAttachmentCollection] class. + PdfAttachmentCollection() : super() { + _dictionary.setProperty(_DictionaryProperties.names, _array); + } + + PdfAttachmentCollection._( + _PdfDictionary attachmentDictionary, _PdfCrossTable crossTable) { + _dictionary = attachmentDictionary; + _crossTable = crossTable; + _initializeAttachmentCollection(); + } + + //Fields + _PdfDictionary _dictionary = _PdfDictionary(); + _PdfArray _array = _PdfArray(); + // ignore: prefer_final_fields + bool _conformance = false; + int _count = 0; + final Map _dic = {}; + _PdfCrossTable _crossTable; + + //Properties + /// Gets the attachment by index from the collection. Read-Only. + PdfAttachment operator [](int index) { + return _list[index] is PdfAttachment ? _list[index] : null; + } + + //Public methods. + /// Add [PdfAttachment] in the specified attachment collection. + /// + /// Returns position of the inserted attachment. + int add(PdfAttachment attachment) { + ArgumentError.checkNotNull(attachment, 'attachment'); + if (_conformance) { + throw ArgumentError( + 'Attachment is not allowed for this conformance level.'); + } + final int position = _doAdd(attachment); + _dictionary.modify(); + return position; + } + + /// Removes the specified attachment from the collection. + void remove(PdfAttachment attachment) { + ArgumentError.checkNotNull(attachment, 'attachment'); + _doRemove(attachment: attachment); + } + + /// Removes attachment at the specified index. + void removeAt(int index) { + _doRemove(index: index); + } + + /// Search and find the index of the attachment. + int indexOf(PdfAttachment attachment) { + ArgumentError.checkNotNull(attachment, 'attachment'); + return _list.indexOf(attachment); + } + + /// Determines whether the attachment collection contains the specified attachment. + /// + /// Returns true if the attachment is present. + bool contains(PdfAttachment attachment) { + ArgumentError.checkNotNull(attachment, 'attachment'); + return _list.contains(attachment); + } + + /// Remove all the attachments from the collection. + void clear() { + _doClear(); + } + + //Implementations. + //Adds the attachment. + int _doAdd(PdfAttachment attachment) { + final String fileName = attachment.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)) { + _dic[(_array[i] as _PdfString).value] = + _array[i + 1] as _PdfReferenceHolder; + } else { + final String value = (_array[i] as _PdfString).value + '_copy'; + _dic[value] = _array[i + 1] as _PdfReferenceHolder; + } + } + } + !_dic.containsKey(converted) + ? _dic[converted] = _PdfReferenceHolder(attachment) + : _dic[converted + '_copy'] = _PdfReferenceHolder(attachment); + final List orderList = _dic.keys.toList(); + orderList.sort(); + _array._clear(); + for (final key in orderList) { + _array._add(_PdfString(key)); + _array._add(_dic[key]); + } + _list.add(attachment); + return _list.length - 1; + } + + //Removes the attachment. + void _doRemove({PdfAttachment attachment, int index}) { + if (attachment != null) { + index = _list.indexOf(attachment); + } + _array._removeAt(2 * index); + _IPdfPrimitive attachmentDictionay = + _PdfCrossTable._dereference(_array[2 * index]); + if (attachmentDictionay is _PdfDictionary) { + _removeAttachementObjects(attachmentDictionay); + attachmentDictionay = null; + } + _array._removeAt(2 * index); + _list.removeAt(index); + } + + //Removing attachment dictionary and stream from main object collection. + void _removeAttachementObjects(_PdfDictionary attachmentDictionary) { + _PdfMainObjectCollection _objectCollection; + if (_crossTable != null && _crossTable._document != null) { + _objectCollection = _crossTable._document._objects; + } + if (_objectCollection != null) { + if (attachmentDictionary != null) { + if (attachmentDictionary.containsKey(_DictionaryProperties.ef)) { + final _IPdfPrimitive embedded = _PdfCrossTable._dereference( + attachmentDictionary[_DictionaryProperties.ef]); + if (embedded is _PdfDictionary) { + if (embedded.containsKey(_DictionaryProperties.f)) { + final _IPdfPrimitive stream = _PdfCrossTable._dereference( + embedded[_DictionaryProperties.f]); + if (stream != null) { + if (_objectCollection.contains(stream)) { + final int index = _objectCollection._lookFor(stream); + if (_objectCollection._objectCollection.length > index) { + _objectCollection._objectCollection.removeAt(index); + } + } + } + } + } + } + if (_objectCollection.contains(attachmentDictionary)) { + final int index = _objectCollection._lookFor(attachmentDictionary); + if (_objectCollection._objectCollection.length > index) { + _objectCollection._objectCollection.removeAt(index); + } + } + } + if (_dic.isNotEmpty) { + _dic.clear(); + } + } + } + + //Clears the collection. + void _doClear() { + _list.clear(); + if (_crossTable != null) { + final _PdfMainObjectCollection coll = _crossTable._document._objects; + if (coll != null) { + for (int i = 1; i < _array.count; i = i + 2) { + if (_array[i] is _PdfReferenceHolder) { + final _IPdfPrimitive dic = _PdfCrossTable._dereference(_array[i]); + if (dic is _PdfDictionary) { + _removeAttachementObjects(dic); + } + } + } + } + } + _array._clear(); + } + + void _initializeAttachmentCollection() { + _IPdfPrimitive embedDictionary = + _dictionary[_DictionaryProperties.embeddedFiles]; + if (embedDictionary is _PdfReferenceHolder) { + embedDictionary = (embedDictionary as _PdfReferenceHolder).object; + } + if (embedDictionary is _PdfDictionary) { + final _IPdfPrimitive obj = + (embedDictionary as _PdfDictionary)[_DictionaryProperties.names]; + final _IPdfPrimitive kid = + (embedDictionary as _PdfDictionary)[_DictionaryProperties.kids]; + if (!(obj is _PdfArray) && kid is _PdfArray) { + final _PdfArray kids = kid; + if (kids != null) { + if (kids.count != 0) { + for (int l = 0; l < kids.count; l++) { + if (kids[l] is _PdfReferenceHolder || kids[l] is _PdfDictionary) { + embedDictionary = kids[l] is _PdfDictionary + ? kids[l] as _PdfDictionary + : (kids[l] as _PdfReferenceHolder).object != null && + (kids[l] as _PdfReferenceHolder).object + is _PdfDictionary + ? (kids[l] as _PdfReferenceHolder).object + : null; + if (embedDictionary != null && + embedDictionary is _PdfDictionary) { + _array = + embedDictionary[_DictionaryProperties.names] as _PdfArray; + if (_array != null) { + _attachmentInformation(_array); + } + } + } + } + } + } + } else if (obj is _PdfArray) { + _array = obj; + _attachmentInformation(_array); + } + } + } + + //Internal method to get attachement information. + 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; + } + if (streamDictionary is _PdfDictionary) { + _PdfStream stream = _PdfStream(); + _PdfDictionary attachmentStream; + if (streamDictionary.containsKey(_DictionaryProperties.ef)) { + if (streamDictionary[_DictionaryProperties.ef] + is _PdfDictionary) { + attachmentStream = streamDictionary[_DictionaryProperties.ef] + as _PdfDictionary; + } else if (streamDictionary[_DictionaryProperties.ef] + is _PdfReferenceHolder) { + final _PdfReferenceHolder streamHolder = + streamDictionary[_DictionaryProperties.ef] + as _PdfReferenceHolder; + attachmentStream = streamHolder.object as _PdfDictionary; + } + final _PdfReferenceHolder holder1 = + attachmentStream[_DictionaryProperties.f] + as _PdfReferenceHolder; + if (holder1 != null) { + if (holder1.object != null && holder1.object is _PdfStream) { + stream = holder1.object; + } + } + } + PdfAttachment attachment; + if (stream != null) { + stream._decompress(); + if (streamDictionary.containsKey('F')) { + attachment = PdfAttachment( + (streamDictionary['F'] as _PdfString).value, + stream._dataStream); + final _IPdfPrimitive fileStream = stream; + if (fileStream is _PdfDictionary) { + final _IPdfPrimitive subtype = _PdfCrossTable._dereference( + fileStream[_DictionaryProperties.subtype]); + if (subtype is _PdfName) { + attachment.mimeType = subtype._name + .replaceAll('#23', '#') + .replaceAll('#20', ' ') + .replaceAll('#2F', '/'); + } + } + if (fileStream is _PdfDictionary && + fileStream.containsKey(_DictionaryProperties.params)) { + final _IPdfPrimitive mParams = _PdfCrossTable._dereference( + fileStream[_DictionaryProperties.params]) + as _PdfDictionary; + if (mParams is _PdfDictionary) { + final _IPdfPrimitive creationDate = + _PdfCrossTable._dereference( + mParams[_DictionaryProperties.creationDate]); + final _IPdfPrimitive modifiedDate = + _PdfCrossTable._dereference( + mParams[_DictionaryProperties.modificationDate]); + if (creationDate is _PdfString) { + attachment.creationDate = + mParams._getDateTime(creationDate); + } + if (modifiedDate is _PdfString) { + attachment.modificationDate = + mParams._getDateTime(modifiedDate); + } + } + } + if (streamDictionary + .containsKey(_DictionaryProperties.afRelationship)) { + final _IPdfPrimitive relationShip = + _PdfCrossTable._dereference(streamDictionary[ + _DictionaryProperties.afRelationship]); + if (relationShip is _PdfName) { + attachment.relationship = + _obtainRelationShip(relationShip._name); + } + } + if (streamDictionary.containsKey('Desc')) { + attachment.description = + (streamDictionary['Desc'] as _PdfString).value; + } + } else { + attachment = PdfAttachment( + (streamDictionary['Desc'] as _PdfString).value, + stream._dataStream); + } + } else { + if (streamDictionary.containsKey('Desc')) { + attachment = PdfAttachment( + (streamDictionary['Desc'] as _PdfString).value, []); + } else { + attachment = PdfAttachment( + (streamDictionary['F'] as _PdfString).value, []); + } + } + _list.add(attachment); + } + } + k = k + 2; + } + } + } + + //Obtain Attachement relation ship + PdfAttachmentRelationship _obtainRelationShip(String relation) { + PdfAttachmentRelationship relationShip = + PdfAttachmentRelationship.unspecified; + switch (relation) { + case 'Alternative': + relationShip = PdfAttachmentRelationship.alternative; + break; + case 'Data': + relationShip = PdfAttachmentRelationship.data; + break; + case 'Source': + relationShip = PdfAttachmentRelationship.source; + break; + case 'Supplement': + relationShip = PdfAttachmentRelationship.supplement; + break; + case 'Unspecified': + relationShip = PdfAttachmentRelationship.unspecified; + break; + default: + break; + } + return relationShip; + } + + //Overrides + @override + _IPdfPrimitive get _element => _dictionary; + + @override + set _element(_IPdfPrimitive value) { + throw ArgumentError('primitive element can\'t be set'); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/enums.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/enums.dart index ceab778dd..59d9c4ad0 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 @@ -50,3 +50,42 @@ enum PdfCrossReferenceType { /// This format is supported by PDF 1.5 version and higher only. crossReferenceStream } + +/// Specifies the PDF document's conformance-level. +enum PdfConformanceLevel { + /// Specifies default / no conformance. + none, + + /// This PDF/A ISO standard (ISO 19005-1:2005) is based on Adobe PDF version 1.4 + /// and This Level B conformance indicates minimal compliance to ensure that the + /// rendered visual appearance of a conforming file is preservable over the long term. + a1b, + + /// PDF/A-2 Standard is based on a PDF 1.7 (ISO 32000-1) + /// which provides support for transparency effects and layers + /// embedding of OpenType fonts. + a2b, + + /// PDF/A-3 Standard is based on a PDF 1.7 (ISO 32000-1) + /// which provides support for embedding the arbitrary file + /// formats (XML, CSV, CAD, Word Processing documents). + a3b, +} + +/// Specifies the file relationship of attachment. +enum PdfAttachmentRelationship { + ///The original source material for the associated content + source, + + ///Represents information used to derive a visual presentation + data, + + ///Alternative representation of the PDF contents + alternative, + + ///supplemental representation of the original source or data that may be more easily consumable + supplement, + + ///Relationship is not known or cannot be described using one of the other values + unspecified +} 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 37a7d09ba..5b21fc176 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 @@ -10,6 +10,8 @@ class PdfBookmark extends PdfBookmarkBase { ArgumentError.checkNotNull(parent, 'parent'); ArgumentError.checkNotNull(title, 'title'); _parent = parent; + _dictionary.setProperty( + _DictionaryProperties.parent, _PdfReferenceHolder(parent)); _previous = previous; _next = next; this.title = title; 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 97a64e0e5..6cb5a00d4 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 @@ -10,12 +10,22 @@ class _PdfCatalog extends _PdfDictionary { _PdfCatalog.fromDocument(PdfDocument document, _PdfDictionary catalog) : super(catalog) { _document = document; + if (containsKey(_DictionaryProperties.names)) { + final _IPdfPrimitive obj = + _PdfCrossTable._dereference(this[_DictionaryProperties.names]); + if (obj is _PdfDictionary) { + _catalogNames = _PdfCatalogNames(obj); + } + } + readMetadata(); freezeChanges(this); } PdfSectionCollection _sections; // ignore: unused_field PdfDocument _document; + _XmpMetadata _metadata; + _PdfCatalogNames _catalogNames; // ignore: unused_element PdfSectionCollection get _pages => _sections; set _pages(PdfSectionCollection sections) { @@ -33,4 +43,67 @@ class _PdfCatalog extends _PdfDictionary { } return dests; } + + _PdfCatalogNames get _names { + if (_catalogNames == null) { + _catalogNames = _PdfCatalogNames(); + this[_DictionaryProperties.names] = _PdfReferenceHolder(_catalogNames); + } + return _catalogNames; + } + + //Implementation + /// Reads Xmp from the document. + void readMetadata() { + //Read metadata if present. + final _IPdfPrimitive rhMetadata = this[_DictionaryProperties.metadata]; + if (_PdfCrossTable._dereference(rhMetadata) is _PdfStream) { + final _PdfStream xmpStream = _PdfCrossTable._dereference(rhMetadata); + bool isFlateDecode = false; + if (xmpStream.containsKey(_DictionaryProperties.filter)) { + _IPdfPrimitive obj = xmpStream[_DictionaryProperties.filter]; + if (obj is _PdfReferenceHolder) { + final _PdfReferenceHolder rh = obj; + obj = rh.object; + } + if (obj != null) { + if (obj is _PdfName) { + final _PdfName filter = obj; + + if (filter._name == _DictionaryProperties.flateDecode) { + isFlateDecode = true; + } + } else if (obj is _PdfArray) { + final _PdfArray filter = obj; + for (final pdfFilter in filter._elements) { + final _PdfName filtername = pdfFilter as _PdfName; + if (filtername._name == _DictionaryProperties.flateDecode) { + isFlateDecode = true; + } + } + } + } + } + + if (xmpStream.compress || isFlateDecode) { + try { + xmpStream._decompress(); + } catch (e) { + //non-compressed stream will throws exception when try to decompress + } + } + XmlDocument xmp; + try { + xmp = XmlDocument.parse(utf8.decode(xmpStream._dataStream)); + } catch (e) { + xmpStream._decompress(); + try { + xmp = XmlDocument.parse(utf8.decode(xmpStream._dataStream)); + } catch (e1) { + return; + } + } + _metadata = _XmpMetadata.fromXmlDocument(xmp); + } + } } 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 new file mode 100644 index 000000000..8d1cd83fa --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_catalog_names.dart @@ -0,0 +1,152 @@ +part of pdf; + +class _PdfCatalogNames implements _IPdfWrapper { + _PdfCatalogNames([_PdfDictionary root]) { + if (root != null) { + _dictionary = root; + } + } + //Fields + _PdfDictionary _dictionary = _PdfDictionary(); + PdfAttachmentCollection _attachments; + + //Properties + //Gets the destinations. + _PdfDictionary get _destinations { + final _IPdfPrimitive obj = + _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.dests]); + final _PdfDictionary dests = obj as _PdfDictionary; + return dests; + } + + set _embeddedFiles(PdfAttachmentCollection value) { + ArgumentError.checkNotNull(value, 'value'); + if (_attachments != value) { + _attachments = value; + _dictionary.setProperty(_DictionaryProperties.embeddedFiles, + _PdfReferenceHolder(_attachments)); + } + } + + //Methods + //Gets the named object from a tree. + _IPdfPrimitive _getNamedObjectFromTree(_PdfDictionary root, _PdfString name) { + bool found = false; + _PdfDictionary current = root; + _IPdfPrimitive obj; + while (!found && current != null && current._items.isNotEmpty) { + if (current.containsKey(_DictionaryProperties.kids)) { + current = _getProperKid(current, name); + } else if (current.containsKey(_DictionaryProperties.names)) { + obj = _findName(current, name); + found = true; + } + } + return obj; + } + + //Finds the name in the tree. + _IPdfPrimitive _findName(_PdfDictionary current, _PdfString name) { + final _PdfArray names = + _PdfCrossTable._dereference(current[_DictionaryProperties.names]) + as _PdfArray; + final int halfLength = names.count ~/ 2; + int lowIndex = 0, topIndex = halfLength - 1, half = 0; + bool found = false; + while (!found) { + half = (lowIndex + topIndex) ~/ 2; + if (lowIndex > topIndex) { + break; + } + final _PdfString str = + _PdfCrossTable._dereference(names[half * 2]) as _PdfString; + final int cmp = _byteCompare(name, str); + if (cmp > 0) { + lowIndex = half + 1; + } else if (cmp < 0) { + topIndex = half - 1; + } else { + found = true; + break; + } + } + _IPdfPrimitive obj; + if (found) { + obj = _PdfCrossTable._dereference(names[half * 2 + 1]); + } + return obj; + } + + //Gets the proper kid from an array. + _PdfDictionary _getProperKid(_PdfDictionary current, _PdfString name) { + final _PdfArray kids = + _PdfCrossTable._dereference(current[_DictionaryProperties.kids]) + as _PdfArray; + _PdfDictionary kid; + for (final obj in kids._elements) { + kid = _PdfCrossTable._dereference(obj) as _PdfDictionary; + if (_checkLimits(kid, name)) { + break; + } else { + kid = null; + } + } + return kid; + } + + // Checks the limits of the named tree node. + bool _checkLimits(_PdfDictionary kid, _PdfString name) { + _IPdfPrimitive obj = kid[_DictionaryProperties.limits]; + bool result = false; + if (obj is _PdfArray && obj.count >= 2) { + final _PdfArray limits = obj; + obj = limits[0]; + final _PdfString lowerLimit = obj as _PdfString; + obj = limits[1]; + final _PdfString higherLimit = obj as _PdfString; + final int lowCmp = _byteCompare(lowerLimit, name); + final int hiCmp = _byteCompare(higherLimit, name); + if (lowCmp == 0 || hiCmp == 0) { + result = true; + } else if (lowCmp < 0 && hiCmp > 0) { + result = true; + } + } + return result; + } + + int _byteCompare(_PdfString str1, _PdfString str2) { + final List data1 = str1.data; + final List data2 = str2.data; + final int commonSize = [data1.length, data2.length].reduce(min); + int result = 0; + for (int i = 0; i < commonSize; ++i) { + final int byte1 = data1[i]; + final int byte2 = data2[i]; + result = byte1 - byte2; + if (result != 0) { + break; + } + } + if (result == 0) { + result = data1.length - data2.length; + } + return result; + } + + // Clear catalog names. + void clear() { + if (_dictionary != null) { + _dictionary.clear(); + } + } + + //Overrides + @override + _IPdfPrimitive get _element => _dictionary; + + @override + set _element(_IPdfPrimitive value) { + throw ArgumentError('primitive elements can\'t be set'); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document.dart index 312c1b874..487e5bad2 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 @@ -8,7 +8,9 @@ class PdfDocument { /// from the PDF data as list of bytes /// /// To initialize a new instance of the [PdfDocument] class for an exisitng - /// PDF document, we can use the optional parameter to load PDF data + /// PDF document, we can use the named parameters + /// to load PDF data and password + /// or add conformance level to the PDF /// /// ```dart /// //Create a new PDF document. @@ -23,13 +25,24 @@ class PdfDocument { /// //Dispose the document. /// document.dispose(); /// ``` - PdfDocument({List inputBytes}) { + PdfDocument( + {List inputBytes, + PdfConformanceLevel conformanceLevel, + String password}) { _isLoadedDocument = inputBytes != null; + _password = password; _initialize(inputBytes); + if (!_isLoadedDocument && conformanceLevel != null) { + _initializeConformance(conformanceLevel); + } } /// Initialize a new instance of the [PdfDocument] class /// from the PDF data as base64 string + /// + /// If the document is encrypted, then we have to provide open or permission + /// passwords to load PDF document + /// /// ```dart /// //Load an exisiting PDF document. /// PdfDocument document = PdfDocument.fromBase64String(pdfData); @@ -43,11 +56,12 @@ class PdfDocument { /// //Dispose the document. /// document.dispose(); /// ``` - PdfDocument.fromBase64String(String base64String) { + PdfDocument.fromBase64String(String base64String, {String password}) { ArgumentError.checkNotNull(base64String); if (base64String.isEmpty) { ArgumentError.value(base64String, 'PDF data', 'PDF data cannot be null'); } + _password = password; _isLoadedDocument = true; _initialize(base64.decode(base64String)); } @@ -71,11 +85,52 @@ class PdfDocument { bool _isStreamCopied = false; PdfBookmarkBase _bookmark; Map _bookmarkHashTable; + PdfDocumentInformation _documentInfo; + PdfSecurity _security; + int _position; + int _orderPosition; + int _onPosition; + int _offPosition; + _PdfArray _primitive; + _PdfArray _printLayer; + _PdfArray _on; + _PdfArray _off; + _PdfArray _order; + String _password; + bool _isEncrypted; + PdfConformanceLevel _conformanceLevel = PdfConformanceLevel.none; + PdfLayerCollection _layers; + _PdfReference _currentSavingObject; + PdfAttachmentCollection _attachments; /// Gets or sets the PDF document compression level. PdfCompressionLevel compressionLevel; //Properties + /// Gets the security features of the document like encryption. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Document security + /// PdfSecurity security = document.security; + /// //Set algorithm + /// security.algorithm = PdfEncryptionAlgorithm.rc4x40Bit; + /// security.userPassword = 'password'; + /// security.ownerPassword = 'syncfusion'; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfSecurity get security { + _security ??= PdfSecurity._(); + if (_conformanceLevel != PdfConformanceLevel.none) { + _security._conformance = true; + } + return _security; + } + /// Gets the collection of the sections in the document. /// /// ```dart @@ -213,9 +268,9 @@ class PdfDocument { if (_isLoadedDocument) { if (_bookmark == null) { if (_catalog.containsKey(_DictionaryProperties.outlines)) { - final _PdfDictionary outlines = _PdfCrossTable._dereference( - _catalog[_DictionaryProperties.outlines]) as _PdfDictionary; - if (outlines != null) { + final _IPdfPrimitive outlines = _PdfCrossTable._dereference( + _catalog[_DictionaryProperties.outlines]); + if (outlines != null && outlines is _PdfDictionary) { _bookmark = PdfBookmarkBase._load(outlines, _crossTable); _bookmark._reproduceTree(); } else { @@ -271,6 +326,69 @@ class PdfDocument { } } + /// Gets document's information and properties such as document's title, subject, keyword etc. + PdfDocumentInformation get documentInformation { + if (_documentInfo == null) { + if (_isLoadedDocument) { + final _PdfDictionary trailer = _crossTable.trailer; + if (_PdfCrossTable._dereference(trailer[_DictionaryProperties.info]) + is _PdfDictionary) { + _documentInfo = PdfDocumentInformation._(_catalog, + dictionary: _PdfCrossTable._dereference( + trailer[_DictionaryProperties.info]), + isLoaded: true, + conformance: _conformanceLevel); + } else { + _documentInfo = PdfDocumentInformation._(_catalog, + conformance: _conformanceLevel); + _crossTable.trailer[_DictionaryProperties.info] = + _PdfReferenceHolder(_documentInfo); + } + // Read document's info dictionary if present. + _readDocumentInfo(); + } else { + _documentInfo = + PdfDocumentInformation._(_catalog, conformance: _conformanceLevel); + _crossTable.trailer[_DictionaryProperties.info] = + _PdfReferenceHolder(_documentInfo); + } + } + return _documentInfo; + } + + /// Gets the collection of PdfLayer from the PDF document. + PdfLayerCollection get layers { + _layers ??= PdfLayerCollection._(this); + return _layers; + } + + /// Gets the attachment collection of the document. + PdfAttachmentCollection get attachments { + if (_attachments == null) { + if (!_isLoadedDocument) { + _attachments = PdfAttachmentCollection(); + if (_conformanceLevel == PdfConformanceLevel.a1b || + _conformanceLevel == PdfConformanceLevel.a2b) { + _attachments._conformance = true; + } + _catalog._names._embeddedFiles = _attachments; + } else { + final _IPdfPrimitive attachmentDictionary = + _PdfCrossTable._dereference(_catalog[_DictionaryProperties.names]); + if (attachmentDictionary is _PdfDictionary && + attachmentDictionary + .containsKey(_DictionaryProperties.embeddedFiles)) { + _attachments = + PdfAttachmentCollection._(attachmentDictionary, _crossTable); + } else { + _attachments = PdfAttachmentCollection(); + _catalog._names._embeddedFiles = _attachments; + } + } + } + return _attachments; + } + //Public methods /// Saves the document and return the saved bytes as list of int. /// @@ -291,15 +409,50 @@ class PdfDocument { writer._document = this; _checkPages(); if (_isLoadedDocument) { - if (fileStructure.incrementalUpdate) { + if (fileStructure.incrementalUpdate && + (_security == null || + (_security != null && + !_security._modifiedSecurity && + !_security.permissions._modifiedPermissions))) { _copyOldStream(writer); } else { - _crossTable = _PdfCrossTable._fromCatalog( - _crossTable.count, _crossTable._documentCatalog); + _crossTable = _PdfCrossTable._fromCatalog(_crossTable.count, + _crossTable.encryptorDictionary, _crossTable._documentCatalog); _crossTable._document = this; + if (documentInformation != null) { + _crossTable.trailer[_DictionaryProperties.info] = + _PdfReferenceHolder(documentInformation); + } } _appendDocument(writer); } else { + if (_conformanceLevel == PdfConformanceLevel.a1b || + _conformanceLevel == PdfConformanceLevel.a2b || + _conformanceLevel == PdfConformanceLevel.a3b) { + documentInformation._xmpMetadata; + if (_conformanceLevel == PdfConformanceLevel.a3b && + _catalog != null && + _catalog._names != null && + attachments != null && + attachments.count > 0) { + final _PdfName fileRelationShip = + _PdfName(_DictionaryProperties.afRelationship); + final _PdfArray fileAttachmentAssociationArray = _PdfArray(); + for (int i = 0; i < attachments.count; i++) { + if (!attachments[i] + ._dictionary + ._items + .containsKey(fileRelationShip)) { + attachments[i]._dictionary._items[fileRelationShip] = + _PdfName('Alternative'); + } + fileAttachmentAssociationArray + ._add(_PdfReferenceHolder(attachments[i]._dictionary)); + } + _catalog._items[_PdfName(_DictionaryProperties.af)] = + fileAttachmentAssociationArray; + } + } _crossTable._save(writer); } return writer._buffer; @@ -332,50 +485,49 @@ class PdfDocument { if (_crossTable != null) { _crossTable._dispose(); } + _security = null; + _currentSavingObject = null; } //Implementation void _initialize(List pdfData) { + _isEncrypted = false; _data = pdfData; _objects = _PdfMainObjectCollection(); if (_isLoadedDocument) { _crossTable = _PdfCrossTable(this, pdfData); - if (_checkEncryption(false)) { - throw ArgumentError.value( - 'Encryption', 'Cannot open an encrypted document.'); - } else { - final _PdfCatalog catalog = _getCatalogValue(); - if (catalog != null && - catalog.containsKey(_DictionaryProperties.pages) && - !catalog.containsKey(_DictionaryProperties.type)) { - catalog._addItems(_DictionaryProperties.type, - _PdfName(_DictionaryProperties.catalog)); - } - if (catalog.containsKey(_DictionaryProperties.type)) { - if (!(catalog[_DictionaryProperties.type] as _PdfName) - ._name - .contains(_DictionaryProperties.catalog)) { - catalog[_DictionaryProperties.type] = - _PdfName(_DictionaryProperties.catalog); - } - _setCatalog(catalog); - } else { - throw ArgumentError.value( - catalog, 'Cannot find the PDF catalog information'); - } - bool hasVersion = false; - if (catalog.containsKey(_DictionaryProperties.version)) { - final _PdfName version = - catalog[_DictionaryProperties.version] as _PdfName; - if (version != null) { - _setFileVersion('PDF-' + version._name); - hasVersion = true; - } + _isEncrypted = _checkEncryption(false); + final _PdfCatalog catalog = _getCatalogValue(); + if (catalog != null && + catalog.containsKey(_DictionaryProperties.pages) && + !catalog.containsKey(_DictionaryProperties.type)) { + catalog._addItems(_DictionaryProperties.type, + _PdfName(_DictionaryProperties.catalog)); + } + if (catalog.containsKey(_DictionaryProperties.type)) { + if (!(catalog[_DictionaryProperties.type] as _PdfName) + ._name + .contains(_DictionaryProperties.catalog)) { + catalog[_DictionaryProperties.type] = + _PdfName(_DictionaryProperties.catalog); } - if (!hasVersion) { - _readFileVersion(); + _setCatalog(catalog); + } else { + throw ArgumentError.value( + catalog, 'Cannot find the PDF catalog information'); + } + bool hasVersion = false; + if (catalog.containsKey(_DictionaryProperties.version)) { + final _PdfName version = + catalog[_DictionaryProperties.version] as _PdfName; + if (version != null) { + _setFileVersion('PDF-' + version._name); + hasVersion = true; } } + if (!hasVersion) { + _readFileVersion(); + } } else { _crossTable = _PdfCrossTable(this); _crossTable._document = this; @@ -387,14 +539,68 @@ class PdfDocument { _catalog._pages = _sections; } compressionLevel = PdfCompressionLevel.normal; + _position = 0; + _orderPosition = 0; + _onPosition = 0; + _offPosition = 0; + _primitive = _PdfArray(); + _on = _PdfArray(); + _off = _PdfArray(); + _order = _PdfArray(); + _printLayer = _PdfArray(); } bool _checkEncryption(bool isAttachEncryption) { - return _crossTable.encryptorDictionary != null; + bool wasEncrypted = false; + if (_crossTable.encryptorDictionary != null) { + final _PdfDictionary encryptionDict = _crossTable.encryptorDictionary; + _password ??= ''; + final _PdfDictionary trailerDict = _crossTable.trailer; + _PdfArray obj; + if (trailerDict.containsKey(_DictionaryProperties.id)) { + _IPdfPrimitive primitive = trailerDict[_DictionaryProperties.id]; + if (primitive is _PdfArray) { + obj = primitive; + } else if (primitive is _PdfReferenceHolder) { + primitive = (primitive as _PdfReferenceHolder).object; + if (primitive != null && primitive is _PdfArray) { + obj = primitive; + } + } + } + obj ??= _PdfArray().._add(_PdfString.fromBytes([])); + final _PdfString key = obj[0] as _PdfString; + final _PdfEncryptor encryptor = _PdfEncryptor(); + if (encryptionDict != null && + encryptionDict.containsKey(_DictionaryProperties.encryptMetadata)) { + _IPdfPrimitive primitive = + encryptionDict[_DictionaryProperties.encryptMetadata]; + if (primitive is _PdfBoolean) { + encryptor.encryptMetadata = primitive.value; + } else if (primitive is _PdfReferenceHolder) { + primitive = (primitive as _PdfReferenceHolder)._object; + if (primitive != null && primitive is _PdfBoolean) { + encryptor.encryptMetadata = primitive.value; + } + } + } + wasEncrypted = true; + encryptor._readFromDictionary(encryptionDict); + if (!encryptor._checkPassword(_password, key)) { + ArgumentError.value(_password, + 'Cannot open an encrypted document. The password is invalid.'); + } + encryptionDict.encrypt = false; + final PdfSecurity security = PdfSecurity._().._encryptor = encryptor; + _security = security; + _crossTable.encryptor = encryptor; + } + return wasEncrypted; } void _copyOldStream(_PdfWriter writer) { writer._write(_data); + writer._write(_Operators.newLine); _isStreamCopied = true; } @@ -499,10 +705,22 @@ class PdfDocument { return _bookmark; } - _PdfArray _getNamedDestination(_PdfName name) { - final _PdfDictionary destinations = _catalog._destinations; - final _IPdfPrimitive obj = destinations[name]; - final _PdfArray destination = _extractDestination(obj); + _PdfArray _getNamedDestination(_IPdfPrimitive obj) { + _PdfDictionary destinations; + _PdfArray destination; + if (obj is _PdfName) { + destinations = _catalog._destinations; + final _IPdfPrimitive name = destinations[obj]; + destination = _extractDestination(name); + } else if (obj is _PdfString) { + final _PdfCatalogNames names = _catalog._names; + if (names != null) { + destinations = names._destinations; + final _IPdfPrimitive name = + names._getNamedObjectFromTree(destinations, obj); + destination = _extractDestination(name); + } + } return destination; } @@ -518,8 +736,10 @@ class PdfDocument { obj = (holder._object as _PdfArray); } } - _PdfArray destination = obj as _PdfArray; - + _PdfArray destination; + if (obj is _PdfArray) { + destination = obj; + } if (dic != null) { obj = _PdfCrossTable._dereference(dic[_DictionaryProperties.d]); destination = obj as _PdfArray; @@ -601,4 +821,56 @@ class PdfDocument { } return _bookmarkHashTable; } + + void _readDocumentInfo() { + // Read document's info if present. + final _PdfDictionary info = _PdfCrossTable._dereference( + _crossTable.trailer[_DictionaryProperties.info]) as _PdfDictionary; + if (info != null && _documentInfo == null) { + _documentInfo = PdfDocumentInformation._(_catalog, + dictionary: info, isLoaded: true, conformance: _conformanceLevel); + } + if (info != null && + !info.changed && + _crossTable.trailer[_DictionaryProperties.info] + is _PdfReferenceHolder) { + _documentInfo = PdfDocumentInformation._(_catalog, + dictionary: info, isLoaded: true, conformance: _conformanceLevel); + if (_objects._lookFor(_documentInfo._element) > -1) { + _objects._reregisterReference( + _objects._lookFor(info), _documentInfo._element); + _documentInfo._element.position = -1; + } + } + } + + void _initializeConformance(PdfConformanceLevel conformance) { + _conformanceLevel = conformance; + if (_conformanceLevel == PdfConformanceLevel.a1b || + _conformanceLevel == PdfConformanceLevel.a2b || + _conformanceLevel == PdfConformanceLevel.a3b) { + //Note : PDF/A is based on Pdf 1.4. Hence it does not support cross reference + //stream which is an Pdf 1.5 feature. + fileStructure.crossReferenceType = + PdfCrossReferenceType.crossReferenceTable; + if (_conformanceLevel == PdfConformanceLevel.a1b) { + fileStructure.version = PdfVersion.version1_4; + } else { + fileStructure.version = PdfVersion.version1_7; + } + //Embed the Color Profie. + final _PdfDictionary dict = _PdfDictionary(); + dict['Info'] = _PdfString('sRGB IEC61966-2.1'); + dict['S'] = _PdfName('GTS_PDFA1'); + dict['OutputConditionIdentifier'] = _PdfString('custom'); + dict['Type'] = _PdfName('OutputIntent'); + dict['OutputCondition'] = _PdfString(''); + dict['RegistryName'] = _PdfString(''); + final _PdfICCColorProfile srgbProfile = _PdfICCColorProfile(); + dict['DestOutputProfile'] = _PdfReferenceHolder(srgbProfile); + final _PdfArray outputIntent = _PdfArray(); + outputIntent._add(dict); + _catalog['OutputIntents'] = outputIntent; + } + } } 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 new file mode 100644 index 000000000..019fd29a4 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document_information.dart @@ -0,0 +1,240 @@ +part of pdf; + +/// A class containing the information about the document. +class PdfDocumentInformation extends _IPdfWrapper { + //Constructor + PdfDocumentInformation._(_PdfCatalog catalog, + {_PdfDictionary dictionary, + bool isLoaded = false, + PdfConformanceLevel conformance}) { + ArgumentError.checkNotNull(catalog, 'catalog'); + _catalog = catalog; + if (conformance != null) { + _conformance = conformance; + } + if (isLoaded) { + ArgumentError.checkNotNull(dictionary, 'dictionary'); + _dictionary = dictionary; + } else { + _dictionary = _PdfDictionary(); + if (_conformance != PdfConformanceLevel.a1b) { + _dictionary._setDateTime( + _DictionaryProperties.creationDate, _creationDate); + } + } + } + + //Fields + _PdfDictionary _dictionary; + _PdfCatalog _catalog; + DateTime _creationDate = DateTime.now(); + DateTime _modificationDate = DateTime.now(); + String _title; + String _author; + String _subject; + String _keywords; + String _creator; + String _producer; + _XmpMetadata _xmp; + bool _isRemoveModifyDate = false; + PdfConformanceLevel _conformance; + + //Properties + /// Gets the creation date of the PDF document + DateTime get creationDate { + if (_dictionary.containsKey(_DictionaryProperties.creationDate) && + _dictionary[_DictionaryProperties.creationDate] is _PdfString) { + return _creationDate = _dictionary + ._getDateTime(_dictionary[_DictionaryProperties.creationDate]); + } + return _creationDate = DateTime.now(); + } + + /// Sets the creation date of the PDF document + set creationDate(DateTime value) { + if (_creationDate != value) { + _creationDate = value; + _dictionary._setDateTime( + _DictionaryProperties.creationDate, _creationDate); + } + } + + /// Gets the modification date of the PDF document + DateTime get modificationDate { + if (_dictionary.containsKey(_DictionaryProperties.modificationDate) && + _dictionary[_DictionaryProperties.modificationDate] is _PdfString) { + return _modificationDate = _dictionary + ._getDateTime(_dictionary[_DictionaryProperties.modificationDate]); + } + return _modificationDate = DateTime.now(); + } + + /// Sets the modification date of the PDF document + set modificationDate(DateTime value) { + _modificationDate = value; + _dictionary._setDateTime( + _DictionaryProperties.modificationDate, _modificationDate); + } + + /// Gets the title. + String get title { + if (_dictionary.containsKey(_DictionaryProperties.title) && + _dictionary[_DictionaryProperties.title] is _PdfString) { + return _title = (_dictionary[_DictionaryProperties.title] as _PdfString) + .value + .replaceAll('\u0000', ''); + } + return _title = ''; + } + + /// Sets the title. + set title(String value) { + if (_title != value) { + _title = value; + _dictionary._setString(_DictionaryProperties.title, _title); + } + } + + /// Gets the author. + String get author { + if (_dictionary.containsKey(_DictionaryProperties.author) && + _dictionary[_DictionaryProperties.author] is _PdfString) { + return _author = + (_dictionary[_DictionaryProperties.author] as _PdfString).value; + } + return _author = ''; + } + + /// Sets the author. + set author(String value) { + if (_author != value) { + _author = value; + _dictionary._setString(_DictionaryProperties.author, _author); + } + } + + /// Gets the subject. + String get subject { + if (_dictionary.containsKey(_DictionaryProperties.subject) && + _dictionary[_DictionaryProperties.subject] is _PdfString) { + return _subject = + (_dictionary[_DictionaryProperties.subject] as _PdfString).value; + } + return _subject = ''; + } + + /// Sets the subject. + set subject(String value) { + if (_subject != value) { + _subject = value; + _dictionary._setString(_DictionaryProperties.subject, _subject); + } + } + + /// Gets the keywords. + String get keywords { + if (_dictionary.containsKey(_DictionaryProperties.keywords) && + _dictionary[_DictionaryProperties.keywords] is _PdfString) { + return _keywords = + (_dictionary[_DictionaryProperties.keywords] as _PdfString).value; + } + return _keywords = ''; + } + + /// Sets the keywords. + set keywords(String value) { + if (_keywords != value) { + _keywords = value; + _dictionary._setString(_DictionaryProperties.keywords, _keywords); + } + if (_catalog != null && _catalog._metadata != null) { + _xmp = _xmpMetadata; + } + } + + /// Gets the creator. + String get creator { + if (_dictionary.containsKey(_DictionaryProperties.creator) && + _dictionary[_DictionaryProperties.creator] is _PdfString) { + return _creator = + (_dictionary[_DictionaryProperties.creator] as _PdfString).value; + } + return _creator = ''; + } + + /// Sets the creator. + set creator(String value) { + if (_creator != value) { + _creator = value; + _dictionary._setString(_DictionaryProperties.creator, _creator); + } + } + + /// Gets the producer. + String get producer { + if (_dictionary.containsKey(_DictionaryProperties.producer) && + _dictionary[_DictionaryProperties.producer] is _PdfString) { + return _producer = + (_dictionary[_DictionaryProperties.producer] as _PdfString).value; + } + return _producer = ''; + } + + /// Sets the producer. + set producer(String value) { + if (_producer != value) { + _producer = value; + _dictionary._setString(_DictionaryProperties.producer, _producer); + } + } + + /// Gets Xmp metadata of the document. + /// + /// Represents the document information in Xmp format. + _XmpMetadata get _xmpMetadata { + if (_xmp == null) { + if (_catalog._metadata == null && _catalog._pages != null) { + _xmp = _XmpMetadata(_catalog._pages._document.documentInformation); + _catalog.setProperty( + _DictionaryProperties.metadata, _PdfReferenceHolder(_xmp)); + } else { + if (_dictionary.changed && !_catalog.changed) { + _xmp = _XmpMetadata(_catalog._document.documentInformation); + _catalog.setProperty( + _DictionaryProperties.metadata, _PdfReferenceHolder(_xmp)); + } else { + _xmp = _catalog._metadata; + _catalog.setProperty( + _DictionaryProperties.metadata, _PdfReferenceHolder(_xmp)); + } + } + } else if (_catalog._metadata != null && _catalog._document != null) { + if (_dictionary.changed && !_catalog.changed) { + _xmp = _XmpMetadata(_catalog._document.documentInformation); + _catalog.setProperty( + _DictionaryProperties.metadata, _PdfReferenceHolder(_xmp)); + } + } + return _xmp; + } + + /// Remove the modification date from existing document. + void removeModificationDate() { + if (_dictionary != null && + _dictionary.containsKey(_DictionaryProperties.modificationDate)) { + _dictionary.remove(_DictionaryProperties.modificationDate); + if (_dictionary.changed && !_catalog.changed) { + _catalog._document.documentInformation._dictionary + .remove(_DictionaryProperties.modificationDate); + _catalog._document.documentInformation._isRemoveModifyDate = true; + _xmp = _XmpMetadata(_catalog._document.documentInformation); + _catalog.setProperty( + _DictionaryProperties.metadata, _PdfReferenceHolder(_xmp)); + } + } + } + + //Overrides + @override + _IPdfPrimitive get _element => _dictionary; +} 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 f150146c6..e37916e2c 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 @@ -28,6 +28,8 @@ class _PdfArray implements _IPdfPrimitive, _IPdfChangable { int _objectCollectionIndex; int _position; _ObjectStatus _status; + _PdfArray _clonedObject; + _PdfCrossTable _crossTable; //Properties _IPdfPrimitive operator [](int index) => _getElement(index); @@ -215,4 +217,20 @@ class _PdfArray implements _IPdfPrimitive, _IPdfChangable { _isChanged = false; } } + + @override + _IPdfPrimitive _clone(_PdfCrossTable crossTable) { + if (_clonedObject != null && _clonedObject._crossTable == crossTable) { + return _clonedObject; + } else { + _clonedObject = null; + } + final _PdfArray newArray = _PdfArray(); + for (final _IPdfPrimitive obj in _elements) { + newArray._add(obj._clone(crossTable)); + } + newArray._crossTable = crossTable; + _clonedObject = newArray; + return newArray; + } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_boolean.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_boolean.dart index 0c045439c..e32c2b281 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_boolean.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_boolean.dart @@ -73,4 +73,7 @@ class _PdfBoolean implements _IPdfPrimitive { _status = null; } } + + @override + _IPdfPrimitive _clone(_PdfCrossTable crossTable) => _PdfBoolean(value); } 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 8f2aa74a8..92ed8602a 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 @@ -5,6 +5,8 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { _PdfDictionary([_PdfDictionary dictionary]) { _items = <_PdfName, _IPdfPrimitive>{}; _copyDictionary(dictionary); + _encrypt = true; + decrypted = false; } //Constants @@ -18,6 +20,10 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { int _objectCollectionIndex; int _position; _ObjectStatus _status; + _PdfCrossTable _crossTable; + bool _archive = true; + bool _encrypt; + bool decrypted; //Properties /// Get the PdfDictionary items. @@ -44,6 +50,13 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { /// Get the values of the item. List<_IPdfPrimitive> get value => _items.values; + bool get encrypt => _encrypt; + + set encrypt(bool value) { + _encrypt = value; + modify(); + } + //Implementation void _copyDictionary(_PdfDictionary dictionary) { if (dictionary != null) { @@ -156,7 +169,15 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { _onBeginSave(args); } if (count > 0) { + final _PdfEncryptor encryptor = writer._document.security._encryptor; + final bool state = encryptor.encrypt; + if (!_encrypt) { + encryptor.encrypt = false; + } _saveItems(writer); + if (!_encrypt) { + encryptor.encrypt = state; + } } writer._write(suffix); writer._write(_Operators.newLine); @@ -198,6 +219,12 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { : 0; } + _PdfString _getString(String propertyName) { + final _IPdfPrimitive primitive = + _PdfCrossTable._dereference(this[propertyName]); + return (primitive != null && primitive is _PdfString) ? primitive : null; + } + bool _checkChanges() { bool result = false; final List<_PdfName> keys = _items.keys.toList(); @@ -235,20 +262,21 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { void _setDateTime(String key, DateTime dateTime) { final _PdfString pdfString = this[key] as _PdfString; final DateFormat dateFormat = DateFormat('yyyyMMddHHmmss'); + final int regionMinutes = dateTime.timeZoneOffset.inMinutes ~/ 11; + String offsetMinutes = regionMinutes.toString(); + if (regionMinutes >= 0 && regionMinutes <= 9) { + offsetMinutes = '0' + offsetMinutes; + } + final int regionHours = dateTime.timeZoneOffset.inHours; + String offsetHours = regionHours.toString(); + if (regionHours >= 0 && regionHours <= 9) { + offsetHours = '0' + offsetHours; + } if (pdfString != null) { - pdfString.value = dateFormat.format(dateTime); + pdfString.value = + "D:${dateFormat.format(dateTime)}+$offsetHours'+$offsetMinutes'"; modify(); } else { - final int regionMinutes = dateTime.minute; - String offsetMinutes = regionMinutes.toString(); - if (regionMinutes >= 0 && regionMinutes <= 9) { - offsetMinutes = '0' + offsetMinutes; - } - final int regionHours = dateTime.hour; - String offsetHours = regionHours.toString(); - if (regionHours >= 0 && regionHours <= 9) { - offsetHours = '0' + offsetHours; - } this[key] = _PdfString('D:' + dateFormat.format(dateTime) + '+' + @@ -277,14 +305,9 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { dateTimeString.value = dateTimeString.value.replaceFirst('191', '20'); } final bool containPrefixD = dateTimeString.value.contains(prefixD); - String dateTimeFormat = 'yyyyMMddHHmmss'; - if (dateTimeString.value.length <= 8) { - dateTimeFormat = 'yyyyMMdd'; - } else if (dateTimeString.value.length <= 10) { - dateTimeFormat = 'yyyyMMddHH'; - } else if (dateTimeString.value.length <= 12) { - dateTimeFormat = 'yyyyMMddHHmm'; - } + final String dateTimeFormat = 'yyyyMMddHHmmss'; + dateTimeString.value = + dateTimeString.value.padRight(dateTimeFormat.length, '0'); String localTime = ''.padRight(dateTimeFormat.length); if (dateTimeString.value.isEmpty) { return DateTime.now(); @@ -296,8 +319,12 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { } final String dateWithT = localTime.substring(0, 8) + 'T' + localTime.substring(8); - final DateTime dateTime = DateTime.parse(dateWithT); - return dateTime; + try { + final DateTime dateTime = DateTime.parse(dateWithT); + return dateTime; + } catch (e) { + return DateTime.now(); + } } //_IPdfChangable members @@ -413,6 +440,37 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { _endSave(this, args); } } + + @override + _IPdfPrimitive _clone(_PdfCrossTable crossTable) { + if (!(this is _PdfStream)) { + if (clonedObject != null && + (clonedObject is _PdfDictionary == true) && + (clonedObject as _PdfDictionary)._crossTable == crossTable) { + return clonedObject; + } else { + clonedObject = null; + } + } + final _PdfDictionary newDict = _PdfDictionary(); + _items.forEach((_PdfName key, _IPdfPrimitive value) { + final _PdfName name = key; + final _IPdfPrimitive obj = value; + final _IPdfPrimitive newObj = obj._clone(crossTable); + if (!(newObj is _PdfNull)) { + newDict[name] = newObj; + } + }); + newDict._archive = _archive; + newDict.status = _status; + newDict.freezeChanges(this); + newDict._crossTable = crossTable; + + if (!(this is _PdfStream)) { + clonedObject = newDict; + } + return newDict; + } } class _SavePdfPrimitiveArgs { 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 a1dbd632f..02d95add9 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 @@ -108,4 +108,7 @@ class _PdfName implements _IPdfPrimitive { _status = null; } } + + @override + _IPdfPrimitive _clone(_PdfCrossTable crossTable) => _PdfName(_name); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_null.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_null.dart index bfc5276cb..7f747d49a 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_null.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_null.dart @@ -68,4 +68,7 @@ class _PdfNull implements _IPdfPrimitive { _status = null; } } + + @override + _IPdfPrimitive _clone(_PdfCrossTable crossTable) => _PdfNull(); } 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 29317fe84..cff8b0752 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 @@ -87,4 +87,7 @@ class _PdfNumber implements _IPdfPrimitive { _status = null; } } + + @override + _IPdfPrimitive _clone(_PdfCrossTable crossTable) => _PdfNumber(value); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference.dart index 822fe6566..040a6c939 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference.dart @@ -87,4 +87,7 @@ class _PdfReference implements _IPdfPrimitive { _status = null; } } + + @override + _IPdfPrimitive _clone(_PdfCrossTable crossTable) => null; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference_holder.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference_holder.dart index ded86b119..ec6358a54 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 @@ -111,7 +111,9 @@ class _PdfReferenceHolder implements _IPdfPrimitive { @override void save(_IPdfWriter writer) { ArgumentError.checkNotNull(writer, 'writer'); - object.isSaving = !writer._document._isLoadedDocument; + if (!writer._document._isLoadedDocument) { + object.isSaving = true; + } final _PdfCrossTable crossTable = writer._document._crossTable; _PdfReference pdfReference; if (writer._document.fileStructure.incrementalUpdate && @@ -149,4 +151,60 @@ class _PdfReferenceHolder implements _IPdfPrimitive { _status = null; } } + + @override + _IPdfPrimitive _clone(_PdfCrossTable crossTable) { + _PdfReferenceHolder refHolder; + _IPdfPrimitive temp; + _PdfReference reference; + if (object is _PdfNumber) { + return _PdfNumber((object as _PdfNumber).value); + } + + if (object is _PdfDictionary) { + // Meaning the referenced page is not available for import. + final _PdfName type = _PdfName(_DictionaryProperties.type); + final _PdfDictionary dict = object as _PdfDictionary; + if (dict.containsKey(type)) { + final _PdfName pageName = dict[type] as _PdfName; + if (pageName != null) { + if (pageName._name == 'Page') { + return _PdfNull(); + } + } + } + } + if (object is _PdfName) { + return _PdfName((object as _PdfName)._name); + } + + // Resolves circular references. + if (crossTable._prevReference != null && + crossTable._prevReference.contains(this.reference)) { + _IPdfPrimitive obj; + if (crossTable._document != null) { + obj = this.crossTable._getObject(this.reference); + } else { + obj = this.crossTable._getObject(this.reference).clonedObject; + } + if (obj != null) { + reference = crossTable._getReference(obj); + return _PdfReferenceHolder.fromReference(reference, crossTable); + } else { + return _PdfNull(); + } + } + if (this.reference != null) { + crossTable._prevReference.add(this.reference); + } + if (!(object is _PdfCatalog)) { + temp = object._clone(crossTable); + } else { + temp = crossTable._document._catalog; + } + + reference = crossTable._getReference(temp); + refHolder = _PdfReferenceHolder.fromReference(reference, crossTable); + return refHolder; + } } 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 fe856ffc8..66bb5a4fa 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 @@ -14,6 +14,8 @@ class _PdfStream extends _PdfDictionary { _copyDictionary(dictionary); this[_DictionaryProperties.length] = _PdfNumber(_dataStream.length); } + decrypted = false; + _blockEncryption = false; } //Constants @@ -23,8 +25,10 @@ class _PdfStream extends _PdfDictionary { //Fields List _dataStream; bool _compress; + _PdfStream _clonedObject; @override bool _isChanged; + bool _blockEncryption; //Properties bool get compress { @@ -270,7 +274,25 @@ class _PdfStream extends _PdfDictionary { final _SavePdfPrimitiveArgs beginSaveArguments = _SavePdfPrimitiveArgs(writer); _onBeginSave(beginSaveArguments); - final List data = _compressContent(writer); + List data = _compressContent(writer); + final PdfSecurity security = writer._document.security; + if (security != null && + security._encryptor != null && + !security._encryptor._encryptMetadata && + containsKey(_DictionaryProperties.type)) { + final _IPdfPrimitive primitive = _items[_DictionaryProperties.type]; + if (primitive != null && primitive is _PdfName) { + final _PdfName fileType = primitive; + if (fileType == null || + (fileType != null && + fileType._name != _DictionaryProperties.metadata)) { + data = _encryptContent(data, writer); + } + } + } else { + data = _encryptContent(data, writer); + } + this[_DictionaryProperties.length] = _PdfNumber(data.length); super._saveDictionary(writer, false); writer._write(prefix); @@ -296,4 +318,40 @@ class _PdfStream extends _PdfDictionary { _dataStream = null; } } + + @override + _IPdfPrimitive _clone(_PdfCrossTable crossTable) { + if (_clonedObject != null && _clonedObject._crossTable == crossTable) { + return _clonedObject; + } else { + _clonedObject = null; + } + final _PdfDictionary dict = super._clone(crossTable) as _PdfDictionary; + final _PdfStream newStream = _PdfStream(dict, _dataStream); + newStream.compress = _compress; + _clonedObject = newStream; + return newStream; + } + + @override + bool decrypted; + + void decrypt(_PdfEncryptor encryptor, int currentObjectNumber) { + if (encryptor != null && !decrypted) { + decrypted = true; + _dataStream = + encryptor._encryptData(currentObjectNumber, _dataStream, false); + _modify(); + } + } + + List _encryptContent(List data, _IPdfWriter writer) { + final PdfDocument doc = writer._document; + final _PdfEncryptor encryptor = doc.security._encryptor; + if (encryptor.encrypt && !_blockEncryption) { + data = + encryptor._encryptData(doc._currentSavingObject._objNum, data, true); + } + 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 edd570c42..2aa736428 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,29 +1,43 @@ part of pdf; class _PdfString implements _IPdfPrimitive { - _PdfString(String value) { + _PdfString(String value, [bool encrypted]) { ArgumentError.checkNotNull(value, 'value'); - if (value.isEmpty) { - this.value = ''; + if (encrypted != null) { + if (!encrypted && value.isNotEmpty) { + data = _hexToBytes(value); + if (data.isNotEmpty) { + this.value = _byteToString(data); + } + } else { + this.value = value; + } + _isHex = true; } else { - this.value = value; - data = []; - for (int i = 0; i < value.length; i++) { - data.add(value.codeUnitAt(i).toUnsigned(8)); + if (value.isEmpty) { + this.value = ''; + } else { + this.value = value; + data = []; + for (int i = 0; i < value.length; i++) { + data.add(value.codeUnitAt(i).toUnsigned(8)); + } } + _isHex = false; } - _isHex = false; + decrypted = false; + isParentDecrypted = false; } _PdfString.fromBytes(List value) { ArgumentError.checkNotNull(value, 'value'); - if (value.isEmpty) { - throw ArgumentError.value(value, 'value is empty'); - } else { - data = value; + data = value; + if (value.isNotEmpty) { this.value = String.fromCharCodes(data); } _isHex = true; + decrypted = false; + isParentDecrypted = false; } //Constants @@ -40,32 +54,58 @@ class _PdfString implements _IPdfPrimitive { _ObjectStatus _status; bool _isHex; _ForceEncoding encode = _ForceEncoding.none; + _PdfCrossTable _crossTable; + _PdfString _clonedObject; + bool isParentDecrypted; //Implementations - List _pdfEncode() { - List result = []; + List _pdfEncode(PdfDocument document) { + final List result = []; result.add(_isHex ? _PdfString.hexStringMark.codeUnitAt(0) : _PdfString.stringMark.codeUnitAt(0)); + List tempData; + bool needStartMark = false; if (_isHex) { - result.addAll(_getHexBytes(data)); + tempData = _getHexBytes(data); } else if (isAsciiEncode) { final List data = _escapeSymbols(value); + tempData = []; for (int i = 0; i < data.length; i++) { - result.add(data[i].toUnsigned(8)); + tempData.add(data[i].toUnsigned(8)); } } else if (utf8.encode(value).length != value.length) { - result = _toUnicodeArray(value, true); - result = _escapeSymbols(result); + tempData = _toUnicodeArray(value, true); + tempData = _escapeSymbols(result); + needStartMark = true; + } else { + tempData = []; + for (int i = 0; i < value.length; i++) { + tempData.add(value.codeUnitAt(i).toUnsigned(8)); + } + } + bool hex = false; + tempData = _encryptIfNeeded(tempData, document); + for (int i = 0; i < tempData.length; i++) { + if ((tempData[i] >= 48 && tempData[i] <= 57) || + (tempData[i] >= 65 && tempData[i] <= 70) || + (tempData[i] >= 97 && tempData[i] <= 102)) { + hex = true; + } else { + hex = false; + break; + } + } + if (_isHex && !hex) { + tempData = _getHexBytes(tempData); + } + result.addAll(tempData); + if (needStartMark) { result.insert( 0, _isHex ? _PdfString.hexStringMark.codeUnitAt(0) : _PdfString.stringMark.codeUnitAt(0)); - } else { - for (int i = 0; i < value.length; i++) { - result.add(value.codeUnitAt(i).toUnsigned(8)); - } } result.add(_isHex ? _PdfString.hexStringMark.codeUnitAt(1) @@ -155,6 +195,51 @@ class _PdfString implements _IPdfPrimitive { return String.fromCharCodes(data, 0, length); } + List _hexToBytes(String value) { + final List hexNumbers = []; + for (int i = 0; i < value.length; i++) { + final int charCode = value.codeUnitAt(i); + if ((charCode >= 48 && charCode <= 57) || + (charCode >= 65 && charCode <= 70) || + (charCode >= 97 && charCode <= 102)) { + hexNumbers.add(_parseHex(charCode)); + } + } + return _hexDigitsToNumbers(hexNumbers); + } + + List _hexDigitsToNumbers(List hexNumbers) { + int value = 0; + bool start = true; + final List list = []; + hexNumbers.forEach((int digit) { + if (start) { + value = (digit << 4).toUnsigned(8); + start = false; + } else { + value += digit; + list.add(value); + start = true; + } + }); + if (!start) { + list.add(value); + } + return list; + } + + int _parseHex(int c) { + int value = 0; + if (c >= 48 && c <= 57) { + value = (c - 48).toUnsigned(8); + } else if (c >= 65 && c <= 70) { + value = (c - 55).toUnsigned(8); + } else if (c >= 97 && c <= 102) { + value = (c - 87).toUnsigned(8); + } + return value; + } + //_IPdfPrimitive members @override bool get isSaving { @@ -206,7 +291,7 @@ class _PdfString implements _IPdfPrimitive { @override void save(_IPdfWriter writer) { ArgumentError.checkNotNull(writer, 'writer'); - writer._write(_pdfEncode()); + writer._write(_pdfEncode(writer._document)); } @override @@ -219,6 +304,45 @@ class _PdfString implements _IPdfPrimitive { _status = null; } } + + @override + _IPdfPrimitive _clone(_PdfCrossTable crossTable) { + if (_clonedObject != null && _clonedObject._crossTable == crossTable) { + return _clonedObject; + } else { + _clonedObject = null; + } + final _PdfString newString = _PdfString(value); + newString.encode = encode; + newString._isHex = _isHex; + newString._crossTable = crossTable; + _clonedObject = newString; + return newString; + } + + List _encryptIfNeeded(List data, PdfDocument document) { + ArgumentError.checkNotNull(data, 'value cannor be null'); + final PdfSecurity security = (document == null) ? null : document.security; + if (security == null || !security._encryptor.encrypt) { + return data; + } else { + data = security._encryptor + ._encryptData(document._currentSavingObject._objNum, data, true); + } + return _escapeSymbols(data); + } + + bool decrypted; + void decrypt(_PdfEncryptor encryptor, int currentObjectNumber) { + if (encryptor != null && !decrypted && !isParentDecrypted) { + decrypted = true; + value = _byteToString(data); + final List bytes = + encryptor._encryptData(currentObjectNumber, data, false); + value = _byteToString(bytes); + data = bytes; + } + } } enum _ForceEncoding { none, ascii, unicode } 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 new file mode 100644 index 000000000..70fad8eb0 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/aes_cipher.dart @@ -0,0 +1,1165 @@ +part of pdf; + +class _AesCipher { + //Constructor + _AesCipher(bool isEncryption, List key, List iv) { + _bp = _BufferedCipher(_CipherBlockChainingMode(_AesEngine())); + final _InvalidParameter ip = _InvalidParameter(_KeyParameter(key), iv); + _bp._initialize(isEncryption, ip); + } + + //Fields + _BufferedCipher _bp; + + //Implementation + List _update(List input, int inputOffset, int inputLength) { + int length = _bp._getUpdateOutputSize(inputLength); + List output; + if (length > 0) { + output = List.filled(length, 0, growable: true); + } else { + length = 0; + } + final Map result = + _bp._processBytes(input, inputOffset, inputLength, output, 0); + output = result['output']; + return output; + } +} + +class _AesCipherNoPadding { + //Constructor + _AesCipherNoPadding(bool isEncryption, List key) { + _cbc = _CipherBlockChainingMode(_AesEngine()); + _cbc._initialize(isEncryption, _KeyParameter(key)); + } + + //Fields + _CipherBlockChainingMode _cbc; + + //Implementation + 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']; + length -= _cbc.blockSize; + tempOffset += _cbc.blockSize; + offset += _cbc.blockSize; + } + return output; + } +} + +class _BufferedCipher { + _BufferedCipher(_CipherBlockChainingMode cipher) { + ArgumentError.checkNotNull(cipher); + _cipher = cipher; + _bytes = List.filled(cipher.blockSize, 0, growable: true); + _offset = 0; + } + + //Fields + _CipherBlockChainingMode _cipher; + List _bytes; + int _offset; + + //Properties + int get blockSize => _cipher.blockSize; + + //Implementation + void _initialize(bool isEncryption, _ICipherParameter parameter) { + _reset(); + _cipher._initialize(isEncryption, parameter); + } + + void _reset() { + _bytes = List.filled(blockSize, 0, growable: true); + _offset = 0; + _cipher._reset(); + } + + int _getUpdateOutputSize(int length) { + final int total = length + _offset; + return total - (total % _bytes.length); + } + + Map _processBytes(List input, int inOffset, int length, + List output, int outOffset) { + if (length < 1) { + return {'output': output, 'length': 0}; + } + int resultLength = 0; + final int gapLength = _bytes.length - _offset; + Map result; + if (length > gapLength) { + List.copyRange(_bytes, _offset, input, inOffset, inOffset + gapLength); + result = _cipher._processBlock(_bytes, 0, output, outOffset); + resultLength += result['length']; + output = result['output']; + _offset = 0; + length -= gapLength; + inOffset += gapLength; + while (length > _bytes.length) { + result = _cipher._processBlock( + input, inOffset, output, outOffset + resultLength); + resultLength += result['length']; + output = result['output']; + length -= blockSize; + inOffset += blockSize; + } + } + List.copyRange(_bytes, _offset, input, inOffset, inOffset + length); + _offset += length; + if (_offset == _bytes.length) { + result = + _cipher._processBlock(_bytes, 0, output, outOffset + resultLength); + resultLength += result['length']; + output = result['output']; + _offset = 0; + } + return {'output': output, 'length': resultLength}; + } +} + +class _AesEncryptor { + _AesEncryptor(List key, List iv, bool isEncryption) { + _initialize(); + _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; + _Aes _aes; + bool _isEncryption; + List _buf; + List _cbcV; + int _ivOff; + List _nextBlockVector; + + //Implementation + 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); + } + + int _getBlockSize(int length) { + final int total = length + _ivOff; + final int leftOver = total % _buf.length; + return total - (leftOver == 0 ? _buf.length : leftOver); + } + + 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 += length; + } + + 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; + } + + int _calculateOutputSize() { + final int total = _ivOff; + final int leftOver = total % _buf.length; + return leftOver == 0 + ? (_isEncryption ? (total + _buf.length) : total) + : (total - leftOver + _buf.length); + } + + int _finalize(List output) { + int resultLen = 0; + final 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); + } + 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; + } +} + +class _Aes { + _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; + } + key = List.filled(nk * 4, 0, growable: true); + List.copyRange(key, 0, keyBytes, 0, key.length); + _initialize(); + } + + //Fields + _KeySize _keySize; + int nb; + int nk; + int nr; + List key; + List> sBox; + List> iBox; + List> rCon; + List> keySheduleArray; + List> state; + + //Implemenation + void _initialize() { + _buildSubstitutionBox(); + _buildInverseSubBox(); + _buildRoundConstants(); + _keyExpansion(); + } + + void _keyExpansion() { + keySheduleArray = + List.generate((nb * (nr + 1)), (i) => List.generate(4, (j) => 0)); + for (int row = 0; row < nk; ++row) { + keySheduleArray[row][0] = key[4 * row]; + keySheduleArray[row][1] = key[(4 * row) + 1]; + 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, (i) => List.generate(nb, (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, (i) => List.generate(nb, (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, (i) => List.generate(4, (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, (i) => List.generate(4, (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, (i) => List.generate(4, (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, (i) => List.generate(4, (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 + ] + ]; + } +} 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 new file mode 100644 index 000000000..b25fb31a4 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/aes_engine.dart @@ -0,0 +1,2974 @@ +part of pdf; + +class _AesEngine { + //Constructor + _AesEngine() { + _initializeConstants(); + } + + //Fields + int blockSize; + List> _key; + int rounds; + bool _isEncryption; + List rcon; + List sBox; + int mix1; + int mix2; + int mix3; + int c0; + int c1; + int c2; + int c3; + int c4; + List r0; + List r1; + List r2; + List r3; + List sinv; + List rinv0; + List rinv1; + List rinv2; + List rinv3; + + //Initialize + void _initialize(bool isEncryption, _ICipherParameter parameter) { + ArgumentError.checkNotNull(parameter); + _key = _generateKey(parameter.keys, isEncryption); + _isEncryption = isEncryption; + } + + List> _generateKey(List keys, bool isEncryption) { + final int keyLength = keys.length ~/ 4; + if ((keyLength != 4 && keyLength != 6 && keyLength != 8) || + keyLength * 4 != keys.length) { + throw ArgumentError.value(keyLength, 'Key length not 128/192/256 bits.'); + } + rounds = keyLength + 6; + final List> newKey = + List.generate((rounds + 1), (i) => List.generate(4, (j) => 0)); + int t = 0; + for (int i = 0; i < keys.length; t++) { + newKey[t >> 2][t & 3] = _convertToUnsignedInt32(keys, i); + i += 4; + } + 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; + } + 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); + } + } + } + return newKey; + } + + Map _processBlock( + List input, int inputOffset, List output, int outputOffset) { + ArgumentError.checkNotNull(_key); + _unPackBlock(input, inputOffset); + if (_isEncryption) { + _encryptBlock(); + } else { + _decryptBlock(); + } + output = _packBlock(output, outputOffset); + return {'length': blockSize, 'output': output}; + } + + 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]; + } + 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]; + } + + 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]; + } + + 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 = 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]; + } + + 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); + } + + 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) { + return (r >> shift).toSigned(32) | (r << (32 - shift)).toSigned(32); + } + + 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 _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 _mulX(int x) { + return ((x & mix2) << 1) ^ (((x & mix1) >> 7) * mix3); + } + + 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); + } + + 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; + } + + void _initializeConstants() { + blockSize = 16; + rounds = 0; + _isEncryption = false; + mix1 = 0x80808080; + mix2 = 0x7f7f7f7f; + mix3 = 0x0000001b; + c0 = 0; + c1 = 0; + c2 = 0; + c3 = 0; + rcon = [ + 0x01, + 0x02, + 0x04, + 0x08, + 0x10, + 0x20, + 0x40, + 0x80, + 0x1b, + 0x36, + 0x6c, + 0xd8, + 0xab, + 0x4d, + 0x9a, + 0x2f, + 0x5e, + 0xbc, + 0x63, + 0xc6, + 0x97, + 0x35, + 0x6a, + 0xd4, + 0xb3, + 0x7d, + 0xfa, + 0xef, + 0xc5, + 0x91 + ]; + sBox = [ + 99, + 124, + 119, + 123, + 242, + 107, + 111, + 197, + 48, + 1, + 103, + 43, + 254, + 215, + 171, + 118, + 202, + 130, + 201, + 125, + 250, + 89, + 71, + 240, + 173, + 212, + 162, + 175, + 156, + 164, + 114, + 192, + 183, + 253, + 147, + 38, + 54, + 63, + 247, + 204, + 52, + 165, + 229, + 241, + 113, + 216, + 49, + 21, + 4, + 199, + 35, + 195, + 24, + 150, + 5, + 154, + 7, + 18, + 128, + 226, + 235, + 39, + 178, + 117, + 9, + 131, + 44, + 26, + 27, + 110, + 90, + 160, + 82, + 59, + 214, + 179, + 41, + 227, + 47, + 132, + 83, + 209, + 0, + 237, + 32, + 252, + 177, + 91, + 106, + 203, + 190, + 57, + 74, + 76, + 88, + 207, + 208, + 239, + 170, + 251, + 67, + 77, + 51, + 133, + 69, + 249, + 2, + 127, + 80, + 60, + 159, + 168, + 81, + 163, + 64, + 143, + 146, + 157, + 56, + 245, + 188, + 182, + 218, + 33, + 16, + 255, + 243, + 210, + 205, + 12, + 19, + 236, + 95, + 151, + 68, + 23, + 196, + 167, + 126, + 61, + 100, + 93, + 25, + 115, + 96, + 129, + 79, + 220, + 34, + 42, + 144, + 136, + 70, + 238, + 184, + 20, + 222, + 94, + 11, + 219, + 224, + 50, + 58, + 10, + 73, + 6, + 36, + 92, + 194, + 211, + 172, + 98, + 145, + 149, + 228, + 121, + 231, + 200, + 55, + 109, + 141, + 213, + 78, + 169, + 108, + 86, + 244, + 234, + 101, + 122, + 174, + 8, + 186, + 120, + 37, + 46, + 28, + 166, + 180, + 198, + 232, + 221, + 116, + 31, + 75, + 189, + 139, + 138, + 112, + 62, + 181, + 102, + 72, + 3, + 246, + 14, + 97, + 53, + 87, + 185, + 134, + 193, + 29, + 158, + 225, + 248, + 152, + 17, + 105, + 217, + 142, + 148, + 155, + 30, + 135, + 233, + 206, + 85, + 40, + 223, + 140, + 161, + 137, + 13, + 191, + 230, + 66, + 104, + 65, + 153, + 45, + 15, + 176, + 84, + 187, + 22 + ]; + r0 = [ + 0xa56363c6, + 0x847c7cf8, + 0x997777ee, + 0x8d7b7bf6, + 0x0df2f2ff, + 0xbd6b6bd6, + 0xb16f6fde, + 0x54c5c591, + 0x50303060, + 0x03010102, + 0xa96767ce, + 0x7d2b2b56, + 0x19fefee7, + 0x62d7d7b5, + 0xe6abab4d, + 0x9a7676ec, + 0x45caca8f, + 0x9d82821f, + 0x40c9c989, + 0x877d7dfa, + 0x15fafaef, + 0xeb5959b2, + 0xc947478e, + 0x0bf0f0fb, + 0xecadad41, + 0x67d4d4b3, + 0xfda2a25f, + 0xeaafaf45, + 0xbf9c9c23, + 0xf7a4a453, + 0x967272e4, + 0x5bc0c09b, + 0xc2b7b775, + 0x1cfdfde1, + 0xae93933d, + 0x6a26264c, + 0x5a36366c, + 0x413f3f7e, + 0x02f7f7f5, + 0x4fcccc83, + 0x5c343468, + 0xf4a5a551, + 0x34e5e5d1, + 0x08f1f1f9, + 0x937171e2, + 0x73d8d8ab, + 0x53313162, + 0x3f15152a, + 0x0c040408, + 0x52c7c795, + 0x65232346, + 0x5ec3c39d, + 0x28181830, + 0xa1969637, + 0x0f05050a, + 0xb59a9a2f, + 0x0907070e, + 0x36121224, + 0x9b80801b, + 0x3de2e2df, + 0x26ebebcd, + 0x6927274e, + 0xcdb2b27f, + 0x9f7575ea, + 0x1b090912, + 0x9e83831d, + 0x742c2c58, + 0x2e1a1a34, + 0x2d1b1b36, + 0xb26e6edc, + 0xee5a5ab4, + 0xfba0a05b, + 0xf65252a4, + 0x4d3b3b76, + 0x61d6d6b7, + 0xceb3b37d, + 0x7b292952, + 0x3ee3e3dd, + 0x712f2f5e, + 0x97848413, + 0xf55353a6, + 0x68d1d1b9, + 0x00000000, + 0x2cededc1, + 0x60202040, + 0x1ffcfce3, + 0xc8b1b179, + 0xed5b5bb6, + 0xbe6a6ad4, + 0x46cbcb8d, + 0xd9bebe67, + 0x4b393972, + 0xde4a4a94, + 0xd44c4c98, + 0xe85858b0, + 0x4acfcf85, + 0x6bd0d0bb, + 0x2aefefc5, + 0xe5aaaa4f, + 0x16fbfbed, + 0xc5434386, + 0xd74d4d9a, + 0x55333366, + 0x94858511, + 0xcf45458a, + 0x10f9f9e9, + 0x06020204, + 0x817f7ffe, + 0xf05050a0, + 0x443c3c78, + 0xba9f9f25, + 0xe3a8a84b, + 0xf35151a2, + 0xfea3a35d, + 0xc0404080, + 0x8a8f8f05, + 0xad92923f, + 0xbc9d9d21, + 0x48383870, + 0x04f5f5f1, + 0xdfbcbc63, + 0xc1b6b677, + 0x75dadaaf, + 0x63212142, + 0x30101020, + 0x1affffe5, + 0x0ef3f3fd, + 0x6dd2d2bf, + 0x4ccdcd81, + 0x140c0c18, + 0x35131326, + 0x2fececc3, + 0xe15f5fbe, + 0xa2979735, + 0xcc444488, + 0x3917172e, + 0x57c4c493, + 0xf2a7a755, + 0x827e7efc, + 0x473d3d7a, + 0xac6464c8, + 0xe75d5dba, + 0x2b191932, + 0x957373e6, + 0xa06060c0, + 0x98818119, + 0xd14f4f9e, + 0x7fdcdca3, + 0x66222244, + 0x7e2a2a54, + 0xab90903b, + 0x8388880b, + 0xca46468c, + 0x29eeeec7, + 0xd3b8b86b, + 0x3c141428, + 0x79dedea7, + 0xe25e5ebc, + 0x1d0b0b16, + 0x76dbdbad, + 0x3be0e0db, + 0x56323264, + 0x4e3a3a74, + 0x1e0a0a14, + 0xdb494992, + 0x0a06060c, + 0x6c242448, + 0xe45c5cb8, + 0x5dc2c29f, + 0x6ed3d3bd, + 0xefacac43, + 0xa66262c4, + 0xa8919139, + 0xa4959531, + 0x37e4e4d3, + 0x8b7979f2, + 0x32e7e7d5, + 0x43c8c88b, + 0x5937376e, + 0xb76d6dda, + 0x8c8d8d01, + 0x64d5d5b1, + 0xd24e4e9c, + 0xe0a9a949, + 0xb46c6cd8, + 0xfa5656ac, + 0x07f4f4f3, + 0x25eaeacf, + 0xaf6565ca, + 0x8e7a7af4, + 0xe9aeae47, + 0x18080810, + 0xd5baba6f, + 0x887878f0, + 0x6f25254a, + 0x722e2e5c, + 0x241c1c38, + 0xf1a6a657, + 0xc7b4b473, + 0x51c6c697, + 0x23e8e8cb, + 0x7cdddda1, + 0x9c7474e8, + 0x211f1f3e, + 0xdd4b4b96, + 0xdcbdbd61, + 0x868b8b0d, + 0x858a8a0f, + 0x907070e0, + 0x423e3e7c, + 0xc4b5b571, + 0xaa6666cc, + 0xd8484890, + 0x05030306, + 0x01f6f6f7, + 0x120e0e1c, + 0xa36161c2, + 0x5f35356a, + 0xf95757ae, + 0xd0b9b969, + 0x91868617, + 0x58c1c199, + 0x271d1d3a, + 0xb99e9e27, + 0x38e1e1d9, + 0x13f8f8eb, + 0xb398982b, + 0x33111122, + 0xbb6969d2, + 0x70d9d9a9, + 0x898e8e07, + 0xa7949433, + 0xb69b9b2d, + 0x221e1e3c, + 0x92878715, + 0x20e9e9c9, + 0x49cece87, + 0xff5555aa, + 0x78282850, + 0x7adfdfa5, + 0x8f8c8c03, + 0xf8a1a159, + 0x80898909, + 0x170d0d1a, + 0xdabfbf65, + 0x31e6e6d7, + 0xc6424284, + 0xb86868d0, + 0xc3414182, + 0xb0999929, + 0x772d2d5a, + 0x110f0f1e, + 0xcbb0b07b, + 0xfc5454a8, + 0xd6bbbb6d, + 0x3a16162c + ]; + r1 = [ + 0x6363c6a5, + 0x7c7cf884, + 0x7777ee99, + 0x7b7bf68d, + 0xf2f2ff0d, + 0x6b6bd6bd, + 0x6f6fdeb1, + 0xc5c59154, + 0x30306050, + 0x01010203, + 0x6767cea9, + 0x2b2b567d, + 0xfefee719, + 0xd7d7b562, + 0xabab4de6, + 0x7676ec9a, + 0xcaca8f45, + 0x82821f9d, + 0xc9c98940, + 0x7d7dfa87, + 0xfafaef15, + 0x5959b2eb, + 0x47478ec9, + 0xf0f0fb0b, + 0xadad41ec, + 0xd4d4b367, + 0xa2a25ffd, + 0xafaf45ea, + 0x9c9c23bf, + 0xa4a453f7, + 0x7272e496, + 0xc0c09b5b, + 0xb7b775c2, + 0xfdfde11c, + 0x93933dae, + 0x26264c6a, + 0x36366c5a, + 0x3f3f7e41, + 0xf7f7f502, + 0xcccc834f, + 0x3434685c, + 0xa5a551f4, + 0xe5e5d134, + 0xf1f1f908, + 0x7171e293, + 0xd8d8ab73, + 0x31316253, + 0x15152a3f, + 0x0404080c, + 0xc7c79552, + 0x23234665, + 0xc3c39d5e, + 0x18183028, + 0x969637a1, + 0x05050a0f, + 0x9a9a2fb5, + 0x07070e09, + 0x12122436, + 0x80801b9b, + 0xe2e2df3d, + 0xebebcd26, + 0x27274e69, + 0xb2b27fcd, + 0x7575ea9f, + 0x0909121b, + 0x83831d9e, + 0x2c2c5874, + 0x1a1a342e, + 0x1b1b362d, + 0x6e6edcb2, + 0x5a5ab4ee, + 0xa0a05bfb, + 0x5252a4f6, + 0x3b3b764d, + 0xd6d6b761, + 0xb3b37dce, + 0x2929527b, + 0xe3e3dd3e, + 0x2f2f5e71, + 0x84841397, + 0x5353a6f5, + 0xd1d1b968, + 0x00000000, + 0xededc12c, + 0x20204060, + 0xfcfce31f, + 0xb1b179c8, + 0x5b5bb6ed, + 0x6a6ad4be, + 0xcbcb8d46, + 0xbebe67d9, + 0x3939724b, + 0x4a4a94de, + 0x4c4c98d4, + 0x5858b0e8, + 0xcfcf854a, + 0xd0d0bb6b, + 0xefefc52a, + 0xaaaa4fe5, + 0xfbfbed16, + 0x434386c5, + 0x4d4d9ad7, + 0x33336655, + 0x85851194, + 0x45458acf, + 0xf9f9e910, + 0x02020406, + 0x7f7ffe81, + 0x5050a0f0, + 0x3c3c7844, + 0x9f9f25ba, + 0xa8a84be3, + 0x5151a2f3, + 0xa3a35dfe, + 0x404080c0, + 0x8f8f058a, + 0x92923fad, + 0x9d9d21bc, + 0x38387048, + 0xf5f5f104, + 0xbcbc63df, + 0xb6b677c1, + 0xdadaaf75, + 0x21214263, + 0x10102030, + 0xffffe51a, + 0xf3f3fd0e, + 0xd2d2bf6d, + 0xcdcd814c, + 0x0c0c1814, + 0x13132635, + 0xececc32f, + 0x5f5fbee1, + 0x979735a2, + 0x444488cc, + 0x17172e39, + 0xc4c49357, + 0xa7a755f2, + 0x7e7efc82, + 0x3d3d7a47, + 0x6464c8ac, + 0x5d5dbae7, + 0x1919322b, + 0x7373e695, + 0x6060c0a0, + 0x81811998, + 0x4f4f9ed1, + 0xdcdca37f, + 0x22224466, + 0x2a2a547e, + 0x90903bab, + 0x88880b83, + 0x46468cca, + 0xeeeec729, + 0xb8b86bd3, + 0x1414283c, + 0xdedea779, + 0x5e5ebce2, + 0x0b0b161d, + 0xdbdbad76, + 0xe0e0db3b, + 0x32326456, + 0x3a3a744e, + 0x0a0a141e, + 0x494992db, + 0x06060c0a, + 0x2424486c, + 0x5c5cb8e4, + 0xc2c29f5d, + 0xd3d3bd6e, + 0xacac43ef, + 0x6262c4a6, + 0x919139a8, + 0x959531a4, + 0xe4e4d337, + 0x7979f28b, + 0xe7e7d532, + 0xc8c88b43, + 0x37376e59, + 0x6d6ddab7, + 0x8d8d018c, + 0xd5d5b164, + 0x4e4e9cd2, + 0xa9a949e0, + 0x6c6cd8b4, + 0x5656acfa, + 0xf4f4f307, + 0xeaeacf25, + 0x6565caaf, + 0x7a7af48e, + 0xaeae47e9, + 0x08081018, + 0xbaba6fd5, + 0x7878f088, + 0x25254a6f, + 0x2e2e5c72, + 0x1c1c3824, + 0xa6a657f1, + 0xb4b473c7, + 0xc6c69751, + 0xe8e8cb23, + 0xdddda17c, + 0x7474e89c, + 0x1f1f3e21, + 0x4b4b96dd, + 0xbdbd61dc, + 0x8b8b0d86, + 0x8a8a0f85, + 0x7070e090, + 0x3e3e7c42, + 0xb5b571c4, + 0x6666ccaa, + 0x484890d8, + 0x03030605, + 0xf6f6f701, + 0x0e0e1c12, + 0x6161c2a3, + 0x35356a5f, + 0x5757aef9, + 0xb9b969d0, + 0x86861791, + 0xc1c19958, + 0x1d1d3a27, + 0x9e9e27b9, + 0xe1e1d938, + 0xf8f8eb13, + 0x98982bb3, + 0x11112233, + 0x6969d2bb, + 0xd9d9a970, + 0x8e8e0789, + 0x949433a7, + 0x9b9b2db6, + 0x1e1e3c22, + 0x87871592, + 0xe9e9c920, + 0xcece8749, + 0x5555aaff, + 0x28285078, + 0xdfdfa57a, + 0x8c8c038f, + 0xa1a159f8, + 0x89890980, + 0x0d0d1a17, + 0xbfbf65da, + 0xe6e6d731, + 0x424284c6, + 0x6868d0b8, + 0x414182c3, + 0x999929b0, + 0x2d2d5a77, + 0x0f0f1e11, + 0xb0b07bcb, + 0x5454a8fc, + 0xbbbb6dd6, + 0x16162c3a + ]; + r2 = [ + 0x63c6a563, + 0x7cf8847c, + 0x77ee9977, + 0x7bf68d7b, + 0xf2ff0df2, + 0x6bd6bd6b, + 0x6fdeb16f, + 0xc59154c5, + 0x30605030, + 0x01020301, + 0x67cea967, + 0x2b567d2b, + 0xfee719fe, + 0xd7b562d7, + 0xab4de6ab, + 0x76ec9a76, + 0xca8f45ca, + 0x821f9d82, + 0xc98940c9, + 0x7dfa877d, + 0xfaef15fa, + 0x59b2eb59, + 0x478ec947, + 0xf0fb0bf0, + 0xad41ecad, + 0xd4b367d4, + 0xa25ffda2, + 0xaf45eaaf, + 0x9c23bf9c, + 0xa453f7a4, + 0x72e49672, + 0xc09b5bc0, + 0xb775c2b7, + 0xfde11cfd, + 0x933dae93, + 0x264c6a26, + 0x366c5a36, + 0x3f7e413f, + 0xf7f502f7, + 0xcc834fcc, + 0x34685c34, + 0xa551f4a5, + 0xe5d134e5, + 0xf1f908f1, + 0x71e29371, + 0xd8ab73d8, + 0x31625331, + 0x152a3f15, + 0x04080c04, + 0xc79552c7, + 0x23466523, + 0xc39d5ec3, + 0x18302818, + 0x9637a196, + 0x050a0f05, + 0x9a2fb59a, + 0x070e0907, + 0x12243612, + 0x801b9b80, + 0xe2df3de2, + 0xebcd26eb, + 0x274e6927, + 0xb27fcdb2, + 0x75ea9f75, + 0x09121b09, + 0x831d9e83, + 0x2c58742c, + 0x1a342e1a, + 0x1b362d1b, + 0x6edcb26e, + 0x5ab4ee5a, + 0xa05bfba0, + 0x52a4f652, + 0x3b764d3b, + 0xd6b761d6, + 0xb37dceb3, + 0x29527b29, + 0xe3dd3ee3, + 0x2f5e712f, + 0x84139784, + 0x53a6f553, + 0xd1b968d1, + 0x00000000, + 0xedc12ced, + 0x20406020, + 0xfce31ffc, + 0xb179c8b1, + 0x5bb6ed5b, + 0x6ad4be6a, + 0xcb8d46cb, + 0xbe67d9be, + 0x39724b39, + 0x4a94de4a, + 0x4c98d44c, + 0x58b0e858, + 0xcf854acf, + 0xd0bb6bd0, + 0xefc52aef, + 0xaa4fe5aa, + 0xfbed16fb, + 0x4386c543, + 0x4d9ad74d, + 0x33665533, + 0x85119485, + 0x458acf45, + 0xf9e910f9, + 0x02040602, + 0x7ffe817f, + 0x50a0f050, + 0x3c78443c, + 0x9f25ba9f, + 0xa84be3a8, + 0x51a2f351, + 0xa35dfea3, + 0x4080c040, + 0x8f058a8f, + 0x923fad92, + 0x9d21bc9d, + 0x38704838, + 0xf5f104f5, + 0xbc63dfbc, + 0xb677c1b6, + 0xdaaf75da, + 0x21426321, + 0x10203010, + 0xffe51aff, + 0xf3fd0ef3, + 0xd2bf6dd2, + 0xcd814ccd, + 0x0c18140c, + 0x13263513, + 0xecc32fec, + 0x5fbee15f, + 0x9735a297, + 0x4488cc44, + 0x172e3917, + 0xc49357c4, + 0xa755f2a7, + 0x7efc827e, + 0x3d7a473d, + 0x64c8ac64, + 0x5dbae75d, + 0x19322b19, + 0x73e69573, + 0x60c0a060, + 0x81199881, + 0x4f9ed14f, + 0xdca37fdc, + 0x22446622, + 0x2a547e2a, + 0x903bab90, + 0x880b8388, + 0x468cca46, + 0xeec729ee, + 0xb86bd3b8, + 0x14283c14, + 0xdea779de, + 0x5ebce25e, + 0x0b161d0b, + 0xdbad76db, + 0xe0db3be0, + 0x32645632, + 0x3a744e3a, + 0x0a141e0a, + 0x4992db49, + 0x060c0a06, + 0x24486c24, + 0x5cb8e45c, + 0xc29f5dc2, + 0xd3bd6ed3, + 0xac43efac, + 0x62c4a662, + 0x9139a891, + 0x9531a495, + 0xe4d337e4, + 0x79f28b79, + 0xe7d532e7, + 0xc88b43c8, + 0x376e5937, + 0x6ddab76d, + 0x8d018c8d, + 0xd5b164d5, + 0x4e9cd24e, + 0xa949e0a9, + 0x6cd8b46c, + 0x56acfa56, + 0xf4f307f4, + 0xeacf25ea, + 0x65caaf65, + 0x7af48e7a, + 0xae47e9ae, + 0x08101808, + 0xba6fd5ba, + 0x78f08878, + 0x254a6f25, + 0x2e5c722e, + 0x1c38241c, + 0xa657f1a6, + 0xb473c7b4, + 0xc69751c6, + 0xe8cb23e8, + 0xdda17cdd, + 0x74e89c74, + 0x1f3e211f, + 0x4b96dd4b, + 0xbd61dcbd, + 0x8b0d868b, + 0x8a0f858a, + 0x70e09070, + 0x3e7c423e, + 0xb571c4b5, + 0x66ccaa66, + 0x4890d848, + 0x03060503, + 0xf6f701f6, + 0x0e1c120e, + 0x61c2a361, + 0x356a5f35, + 0x57aef957, + 0xb969d0b9, + 0x86179186, + 0xc19958c1, + 0x1d3a271d, + 0x9e27b99e, + 0xe1d938e1, + 0xf8eb13f8, + 0x982bb398, + 0x11223311, + 0x69d2bb69, + 0xd9a970d9, + 0x8e07898e, + 0x9433a794, + 0x9b2db69b, + 0x1e3c221e, + 0x87159287, + 0xe9c920e9, + 0xce8749ce, + 0x55aaff55, + 0x28507828, + 0xdfa57adf, + 0x8c038f8c, + 0xa159f8a1, + 0x89098089, + 0x0d1a170d, + 0xbf65dabf, + 0xe6d731e6, + 0x4284c642, + 0x68d0b868, + 0x4182c341, + 0x9929b099, + 0x2d5a772d, + 0x0f1e110f, + 0xb07bcbb0, + 0x54a8fc54, + 0xbb6dd6bb, + 0x162c3a16 + ]; + r3 = [ + 0xc6a56363, + 0xf8847c7c, + 0xee997777, + 0xf68d7b7b, + 0xff0df2f2, + 0xd6bd6b6b, + 0xdeb16f6f, + 0x9154c5c5, + 0x60503030, + 0x02030101, + 0xcea96767, + 0x567d2b2b, + 0xe719fefe, + 0xb562d7d7, + 0x4de6abab, + 0xec9a7676, + 0x8f45caca, + 0x1f9d8282, + 0x8940c9c9, + 0xfa877d7d, + 0xef15fafa, + 0xb2eb5959, + 0x8ec94747, + 0xfb0bf0f0, + 0x41ecadad, + 0xb367d4d4, + 0x5ffda2a2, + 0x45eaafaf, + 0x23bf9c9c, + 0x53f7a4a4, + 0xe4967272, + 0x9b5bc0c0, + 0x75c2b7b7, + 0xe11cfdfd, + 0x3dae9393, + 0x4c6a2626, + 0x6c5a3636, + 0x7e413f3f, + 0xf502f7f7, + 0x834fcccc, + 0x685c3434, + 0x51f4a5a5, + 0xd134e5e5, + 0xf908f1f1, + 0xe2937171, + 0xab73d8d8, + 0x62533131, + 0x2a3f1515, + 0x080c0404, + 0x9552c7c7, + 0x46652323, + 0x9d5ec3c3, + 0x30281818, + 0x37a19696, + 0x0a0f0505, + 0x2fb59a9a, + 0x0e090707, + 0x24361212, + 0x1b9b8080, + 0xdf3de2e2, + 0xcd26ebeb, + 0x4e692727, + 0x7fcdb2b2, + 0xea9f7575, + 0x121b0909, + 0x1d9e8383, + 0x58742c2c, + 0x342e1a1a, + 0x362d1b1b, + 0xdcb26e6e, + 0xb4ee5a5a, + 0x5bfba0a0, + 0xa4f65252, + 0x764d3b3b, + 0xb761d6d6, + 0x7dceb3b3, + 0x527b2929, + 0xdd3ee3e3, + 0x5e712f2f, + 0x13978484, + 0xa6f55353, + 0xb968d1d1, + 0x00000000, + 0xc12ceded, + 0x40602020, + 0xe31ffcfc, + 0x79c8b1b1, + 0xb6ed5b5b, + 0xd4be6a6a, + 0x8d46cbcb, + 0x67d9bebe, + 0x724b3939, + 0x94de4a4a, + 0x98d44c4c, + 0xb0e85858, + 0x854acfcf, + 0xbb6bd0d0, + 0xc52aefef, + 0x4fe5aaaa, + 0xed16fbfb, + 0x86c54343, + 0x9ad74d4d, + 0x66553333, + 0x11948585, + 0x8acf4545, + 0xe910f9f9, + 0x04060202, + 0xfe817f7f, + 0xa0f05050, + 0x78443c3c, + 0x25ba9f9f, + 0x4be3a8a8, + 0xa2f35151, + 0x5dfea3a3, + 0x80c04040, + 0x058a8f8f, + 0x3fad9292, + 0x21bc9d9d, + 0x70483838, + 0xf104f5f5, + 0x63dfbcbc, + 0x77c1b6b6, + 0xaf75dada, + 0x42632121, + 0x20301010, + 0xe51affff, + 0xfd0ef3f3, + 0xbf6dd2d2, + 0x814ccdcd, + 0x18140c0c, + 0x26351313, + 0xc32fecec, + 0xbee15f5f, + 0x35a29797, + 0x88cc4444, + 0x2e391717, + 0x9357c4c4, + 0x55f2a7a7, + 0xfc827e7e, + 0x7a473d3d, + 0xc8ac6464, + 0xbae75d5d, + 0x322b1919, + 0xe6957373, + 0xc0a06060, + 0x19988181, + 0x9ed14f4f, + 0xa37fdcdc, + 0x44662222, + 0x547e2a2a, + 0x3bab9090, + 0x0b838888, + 0x8cca4646, + 0xc729eeee, + 0x6bd3b8b8, + 0x283c1414, + 0xa779dede, + 0xbce25e5e, + 0x161d0b0b, + 0xad76dbdb, + 0xdb3be0e0, + 0x64563232, + 0x744e3a3a, + 0x141e0a0a, + 0x92db4949, + 0x0c0a0606, + 0x486c2424, + 0xb8e45c5c, + 0x9f5dc2c2, + 0xbd6ed3d3, + 0x43efacac, + 0xc4a66262, + 0x39a89191, + 0x31a49595, + 0xd337e4e4, + 0xf28b7979, + 0xd532e7e7, + 0x8b43c8c8, + 0x6e593737, + 0xdab76d6d, + 0x018c8d8d, + 0xb164d5d5, + 0x9cd24e4e, + 0x49e0a9a9, + 0xd8b46c6c, + 0xacfa5656, + 0xf307f4f4, + 0xcf25eaea, + 0xcaaf6565, + 0xf48e7a7a, + 0x47e9aeae, + 0x10180808, + 0x6fd5baba, + 0xf0887878, + 0x4a6f2525, + 0x5c722e2e, + 0x38241c1c, + 0x57f1a6a6, + 0x73c7b4b4, + 0x9751c6c6, + 0xcb23e8e8, + 0xa17cdddd, + 0xe89c7474, + 0x3e211f1f, + 0x96dd4b4b, + 0x61dcbdbd, + 0x0d868b8b, + 0x0f858a8a, + 0xe0907070, + 0x7c423e3e, + 0x71c4b5b5, + 0xccaa6666, + 0x90d84848, + 0x06050303, + 0xf701f6f6, + 0x1c120e0e, + 0xc2a36161, + 0x6a5f3535, + 0xaef95757, + 0x69d0b9b9, + 0x17918686, + 0x9958c1c1, + 0x3a271d1d, + 0x27b99e9e, + 0xd938e1e1, + 0xeb13f8f8, + 0x2bb39898, + 0x22331111, + 0xd2bb6969, + 0xa970d9d9, + 0x07898e8e, + 0x33a79494, + 0x2db69b9b, + 0x3c221e1e, + 0x15928787, + 0xc920e9e9, + 0x8749cece, + 0xaaff5555, + 0x50782828, + 0xa57adfdf, + 0x038f8c8c, + 0x59f8a1a1, + 0x09808989, + 0x1a170d0d, + 0x65dabfbf, + 0xd731e6e6, + 0x84c64242, + 0xd0b86868, + 0x82c34141, + 0x29b09999, + 0x5a772d2d, + 0x1e110f0f, + 0x7bcbb0b0, + 0xa8fc5454, + 0x6dd6bbbb, + 0x2c3a1616 + ]; + sinv = [ + 82, + 9, + 106, + 213, + 48, + 54, + 165, + 56, + 191, + 64, + 163, + 158, + 129, + 243, + 215, + 251, + 124, + 227, + 57, + 130, + 155, + 47, + 255, + 135, + 52, + 142, + 67, + 68, + 196, + 222, + 233, + 203, + 84, + 123, + 148, + 50, + 166, + 194, + 35, + 61, + 238, + 76, + 149, + 11, + 66, + 250, + 195, + 78, + 8, + 46, + 161, + 102, + 40, + 217, + 36, + 178, + 118, + 91, + 162, + 73, + 109, + 139, + 209, + 37, + 114, + 248, + 246, + 100, + 134, + 104, + 152, + 22, + 212, + 164, + 92, + 204, + 93, + 101, + 182, + 146, + 108, + 112, + 72, + 80, + 253, + 237, + 185, + 218, + 94, + 21, + 70, + 87, + 167, + 141, + 157, + 132, + 144, + 216, + 171, + 0, + 140, + 188, + 211, + 10, + 247, + 228, + 88, + 5, + 184, + 179, + 69, + 6, + 208, + 44, + 30, + 143, + 202, + 63, + 15, + 2, + 193, + 175, + 189, + 3, + 1, + 19, + 138, + 107, + 58, + 145, + 17, + 65, + 79, + 103, + 220, + 234, + 151, + 242, + 207, + 206, + 240, + 180, + 230, + 115, + 150, + 172, + 116, + 34, + 231, + 173, + 53, + 133, + 226, + 249, + 55, + 232, + 28, + 117, + 223, + 110, + 71, + 241, + 26, + 113, + 29, + 41, + 197, + 137, + 111, + 183, + 98, + 14, + 170, + 24, + 190, + 27, + 252, + 86, + 62, + 75, + 198, + 210, + 121, + 32, + 154, + 219, + 192, + 254, + 120, + 205, + 90, + 244, + 31, + 221, + 168, + 51, + 136, + 7, + 199, + 49, + 177, + 18, + 16, + 89, + 39, + 128, + 236, + 95, + 96, + 81, + 127, + 169, + 25, + 181, + 74, + 13, + 45, + 229, + 122, + 159, + 147, + 201, + 156, + 239, + 160, + 224, + 59, + 77, + 174, + 42, + 245, + 176, + 200, + 235, + 187, + 60, + 131, + 83, + 153, + 97, + 23, + 43, + 4, + 126, + 186, + 119, + 214, + 38, + 225, + 105, + 20, + 99, + 85, + 33, + 12, + 125 + ]; + rinv0 = [ + 0x50a7f451, + 0x5365417e, + 0xc3a4171a, + 0x965e273a, + 0xcb6bab3b, + 0xf1459d1f, + 0xab58faac, + 0x9303e34b, + 0x55fa3020, + 0xf66d76ad, + 0x9176cc88, + 0x254c02f5, + 0xfcd7e54f, + 0xd7cb2ac5, + 0x80443526, + 0x8fa362b5, + 0x495ab1de, + 0x671bba25, + 0x980eea45, + 0xe1c0fe5d, + 0x02752fc3, + 0x12f04c81, + 0xa397468d, + 0xc6f9d36b, + 0xe75f8f03, + 0x959c9215, + 0xeb7a6dbf, + 0xda595295, + 0x2d83bed4, + 0xd3217458, + 0x2969e049, + 0x44c8c98e, + 0x6a89c275, + 0x78798ef4, + 0x6b3e5899, + 0xdd71b927, + 0xb64fe1be, + 0x17ad88f0, + 0x66ac20c9, + 0xb43ace7d, + 0x184adf63, + 0x82311ae5, + 0x60335197, + 0x457f5362, + 0xe07764b1, + 0x84ae6bbb, + 0x1ca081fe, + 0x942b08f9, + 0x58684870, + 0x19fd458f, + 0x876cde94, + 0xb7f87b52, + 0x23d373ab, + 0xe2024b72, + 0x578f1fe3, + 0x2aab5566, + 0x0728ebb2, + 0x03c2b52f, + 0x9a7bc586, + 0xa50837d3, + 0xf2872830, + 0xb2a5bf23, + 0xba6a0302, + 0x5c8216ed, + 0x2b1ccf8a, + 0x92b479a7, + 0xf0f207f3, + 0xa1e2694e, + 0xcdf4da65, + 0xd5be0506, + 0x1f6234d1, + 0x8afea6c4, + 0x9d532e34, + 0xa055f3a2, + 0x32e18a05, + 0x75ebf6a4, + 0x39ec830b, + 0xaaef6040, + 0x069f715e, + 0x51106ebd, + 0xf98a213e, + 0x3d06dd96, + 0xae053edd, + 0x46bde64d, + 0xb58d5491, + 0x055dc471, + 0x6fd40604, + 0xff155060, + 0x24fb9819, + 0x97e9bdd6, + 0xcc434089, + 0x779ed967, + 0xbd42e8b0, + 0x888b8907, + 0x385b19e7, + 0xdbeec879, + 0x470a7ca1, + 0xe90f427c, + 0xc91e84f8, + 0x00000000, + 0x83868009, + 0x48ed2b32, + 0xac70111e, + 0x4e725a6c, + 0xfbff0efd, + 0x5638850f, + 0x1ed5ae3d, + 0x27392d36, + 0x64d90f0a, + 0x21a65c68, + 0xd1545b9b, + 0x3a2e3624, + 0xb1670a0c, + 0x0fe75793, + 0xd296eeb4, + 0x9e919b1b, + 0x4fc5c080, + 0xa220dc61, + 0x694b775a, + 0x161a121c, + 0x0aba93e2, + 0xe52aa0c0, + 0x43e0223c, + 0x1d171b12, + 0x0b0d090e, + 0xadc78bf2, + 0xb9a8b62d, + 0xc8a91e14, + 0x8519f157, + 0x4c0775af, + 0xbbdd99ee, + 0xfd607fa3, + 0x9f2601f7, + 0xbcf5725c, + 0xc53b6644, + 0x347efb5b, + 0x7629438b, + 0xdcc623cb, + 0x68fcedb6, + 0x63f1e4b8, + 0xcadc31d7, + 0x10856342, + 0x40229713, + 0x2011c684, + 0x7d244a85, + 0xf83dbbd2, + 0x1132f9ae, + 0x6da129c7, + 0x4b2f9e1d, + 0xf330b2dc, + 0xec52860d, + 0xd0e3c177, + 0x6c16b32b, + 0x99b970a9, + 0xfa489411, + 0x2264e947, + 0xc48cfca8, + 0x1a3ff0a0, + 0xd82c7d56, + 0xef903322, + 0xc74e4987, + 0xc1d138d9, + 0xfea2ca8c, + 0x360bd498, + 0xcf81f5a6, + 0x28de7aa5, + 0x268eb7da, + 0xa4bfad3f, + 0xe49d3a2c, + 0x0d927850, + 0x9bcc5f6a, + 0x62467e54, + 0xc2138df6, + 0xe8b8d890, + 0x5ef7392e, + 0xf5afc382, + 0xbe805d9f, + 0x7c93d069, + 0xa92dd56f, + 0xb31225cf, + 0x3b99acc8, + 0xa77d1810, + 0x6e639ce8, + 0x7bbb3bdb, + 0x097826cd, + 0xf418596e, + 0x01b79aec, + 0xa89a4f83, + 0x656e95e6, + 0x7ee6ffaa, + 0x08cfbc21, + 0xe6e815ef, + 0xd99be7ba, + 0xce366f4a, + 0xd4099fea, + 0xd67cb029, + 0xafb2a431, + 0x31233f2a, + 0x3094a5c6, + 0xc066a235, + 0x37bc4e74, + 0xa6ca82fc, + 0xb0d090e0, + 0x15d8a733, + 0x4a9804f1, + 0xf7daec41, + 0x0e50cd7f, + 0x2ff69117, + 0x8dd64d76, + 0x4db0ef43, + 0x544daacc, + 0xdf0496e4, + 0xe3b5d19e, + 0x1b886a4c, + 0xb81f2cc1, + 0x7f516546, + 0x04ea5e9d, + 0x5d358c01, + 0x737487fa, + 0x2e410bfb, + 0x5a1d67b3, + 0x52d2db92, + 0x335610e9, + 0x1347d66d, + 0x8c61d79a, + 0x7a0ca137, + 0x8e14f859, + 0x893c13eb, + 0xee27a9ce, + 0x35c961b7, + 0xede51ce1, + 0x3cb1477a, + 0x59dfd29c, + 0x3f73f255, + 0x79ce1418, + 0xbf37c773, + 0xeacdf753, + 0x5baafd5f, + 0x146f3ddf, + 0x86db4478, + 0x81f3afca, + 0x3ec468b9, + 0x2c342438, + 0x5f40a3c2, + 0x72c31d16, + 0x0c25e2bc, + 0x8b493c28, + 0x41950dff, + 0x7101a839, + 0xdeb30c08, + 0x9ce4b4d8, + 0x90c15664, + 0x6184cb7b, + 0x70b632d5, + 0x745c6c48, + 0x4257b8d0 + ]; + rinv1 = [ + 0xa7f45150, + 0x65417e53, + 0xa4171ac3, + 0x5e273a96, + 0x6bab3bcb, + 0x459d1ff1, + 0x58faacab, + 0x03e34b93, + 0xfa302055, + 0x6d76adf6, + 0x76cc8891, + 0x4c02f525, + 0xd7e54ffc, + 0xcb2ac5d7, + 0x44352680, + 0xa362b58f, + 0x5ab1de49, + 0x1bba2567, + 0x0eea4598, + 0xc0fe5de1, + 0x752fc302, + 0xf04c8112, + 0x97468da3, + 0xf9d36bc6, + 0x5f8f03e7, + 0x9c921595, + 0x7a6dbfeb, + 0x595295da, + 0x83bed42d, + 0x217458d3, + 0x69e04929, + 0xc8c98e44, + 0x89c2756a, + 0x798ef478, + 0x3e58996b, + 0x71b927dd, + 0x4fe1beb6, + 0xad88f017, + 0xac20c966, + 0x3ace7db4, + 0x4adf6318, + 0x311ae582, + 0x33519760, + 0x7f536245, + 0x7764b1e0, + 0xae6bbb84, + 0xa081fe1c, + 0x2b08f994, + 0x68487058, + 0xfd458f19, + 0x6cde9487, + 0xf87b52b7, + 0xd373ab23, + 0x024b72e2, + 0x8f1fe357, + 0xab55662a, + 0x28ebb207, + 0xc2b52f03, + 0x7bc5869a, + 0x0837d3a5, + 0x872830f2, + 0xa5bf23b2, + 0x6a0302ba, + 0x8216ed5c, + 0x1ccf8a2b, + 0xb479a792, + 0xf207f3f0, + 0xe2694ea1, + 0xf4da65cd, + 0xbe0506d5, + 0x6234d11f, + 0xfea6c48a, + 0x532e349d, + 0x55f3a2a0, + 0xe18a0532, + 0xebf6a475, + 0xec830b39, + 0xef6040aa, + 0x9f715e06, + 0x106ebd51, + 0x8a213ef9, + 0x06dd963d, + 0x053eddae, + 0xbde64d46, + 0x8d5491b5, + 0x5dc47105, + 0xd406046f, + 0x155060ff, + 0xfb981924, + 0xe9bdd697, + 0x434089cc, + 0x9ed96777, + 0x42e8b0bd, + 0x8b890788, + 0x5b19e738, + 0xeec879db, + 0x0a7ca147, + 0x0f427ce9, + 0x1e84f8c9, + 0x00000000, + 0x86800983, + 0xed2b3248, + 0x70111eac, + 0x725a6c4e, + 0xff0efdfb, + 0x38850f56, + 0xd5ae3d1e, + 0x392d3627, + 0xd90f0a64, + 0xa65c6821, + 0x545b9bd1, + 0x2e36243a, + 0x670a0cb1, + 0xe757930f, + 0x96eeb4d2, + 0x919b1b9e, + 0xc5c0804f, + 0x20dc61a2, + 0x4b775a69, + 0x1a121c16, + 0xba93e20a, + 0x2aa0c0e5, + 0xe0223c43, + 0x171b121d, + 0x0d090e0b, + 0xc78bf2ad, + 0xa8b62db9, + 0xa91e14c8, + 0x19f15785, + 0x0775af4c, + 0xdd99eebb, + 0x607fa3fd, + 0x2601f79f, + 0xf5725cbc, + 0x3b6644c5, + 0x7efb5b34, + 0x29438b76, + 0xc623cbdc, + 0xfcedb668, + 0xf1e4b863, + 0xdc31d7ca, + 0x85634210, + 0x22971340, + 0x11c68420, + 0x244a857d, + 0x3dbbd2f8, + 0x32f9ae11, + 0xa129c76d, + 0x2f9e1d4b, + 0x30b2dcf3, + 0x52860dec, + 0xe3c177d0, + 0x16b32b6c, + 0xb970a999, + 0x489411fa, + 0x64e94722, + 0x8cfca8c4, + 0x3ff0a01a, + 0x2c7d56d8, + 0x903322ef, + 0x4e4987c7, + 0xd138d9c1, + 0xa2ca8cfe, + 0x0bd49836, + 0x81f5a6cf, + 0xde7aa528, + 0x8eb7da26, + 0xbfad3fa4, + 0x9d3a2ce4, + 0x9278500d, + 0xcc5f6a9b, + 0x467e5462, + 0x138df6c2, + 0xb8d890e8, + 0xf7392e5e, + 0xafc382f5, + 0x805d9fbe, + 0x93d0697c, + 0x2dd56fa9, + 0x1225cfb3, + 0x99acc83b, + 0x7d1810a7, + 0x639ce86e, + 0xbb3bdb7b, + 0x7826cd09, + 0x18596ef4, + 0xb79aec01, + 0x9a4f83a8, + 0x6e95e665, + 0xe6ffaa7e, + 0xcfbc2108, + 0xe815efe6, + 0x9be7bad9, + 0x366f4ace, + 0x099fead4, + 0x7cb029d6, + 0xb2a431af, + 0x233f2a31, + 0x94a5c630, + 0x66a235c0, + 0xbc4e7437, + 0xca82fca6, + 0xd090e0b0, + 0xd8a73315, + 0x9804f14a, + 0xdaec41f7, + 0x50cd7f0e, + 0xf691172f, + 0xd64d768d, + 0xb0ef434d, + 0x4daacc54, + 0x0496e4df, + 0xb5d19ee3, + 0x886a4c1b, + 0x1f2cc1b8, + 0x5165467f, + 0xea5e9d04, + 0x358c015d, + 0x7487fa73, + 0x410bfb2e, + 0x1d67b35a, + 0xd2db9252, + 0x5610e933, + 0x47d66d13, + 0x61d79a8c, + 0x0ca1377a, + 0x14f8598e, + 0x3c13eb89, + 0x27a9ceee, + 0xc961b735, + 0xe51ce1ed, + 0xb1477a3c, + 0xdfd29c59, + 0x73f2553f, + 0xce141879, + 0x37c773bf, + 0xcdf753ea, + 0xaafd5f5b, + 0x6f3ddf14, + 0xdb447886, + 0xf3afca81, + 0xc468b93e, + 0x3424382c, + 0x40a3c25f, + 0xc31d1672, + 0x25e2bc0c, + 0x493c288b, + 0x950dff41, + 0x01a83971, + 0xb30c08de, + 0xe4b4d89c, + 0xc1566490, + 0x84cb7b61, + 0xb632d570, + 0x5c6c4874, + 0x57b8d042 + ]; + rinv2 = [ + 0xf45150a7, + 0x417e5365, + 0x171ac3a4, + 0x273a965e, + 0xab3bcb6b, + 0x9d1ff145, + 0xfaacab58, + 0xe34b9303, + 0x302055fa, + 0x76adf66d, + 0xcc889176, + 0x02f5254c, + 0xe54ffcd7, + 0x2ac5d7cb, + 0x35268044, + 0x62b58fa3, + 0xb1de495a, + 0xba25671b, + 0xea45980e, + 0xfe5de1c0, + 0x2fc30275, + 0x4c8112f0, + 0x468da397, + 0xd36bc6f9, + 0x8f03e75f, + 0x9215959c, + 0x6dbfeb7a, + 0x5295da59, + 0xbed42d83, + 0x7458d321, + 0xe0492969, + 0xc98e44c8, + 0xc2756a89, + 0x8ef47879, + 0x58996b3e, + 0xb927dd71, + 0xe1beb64f, + 0x88f017ad, + 0x20c966ac, + 0xce7db43a, + 0xdf63184a, + 0x1ae58231, + 0x51976033, + 0x5362457f, + 0x64b1e077, + 0x6bbb84ae, + 0x81fe1ca0, + 0x08f9942b, + 0x48705868, + 0x458f19fd, + 0xde94876c, + 0x7b52b7f8, + 0x73ab23d3, + 0x4b72e202, + 0x1fe3578f, + 0x55662aab, + 0xebb20728, + 0xb52f03c2, + 0xc5869a7b, + 0x37d3a508, + 0x2830f287, + 0xbf23b2a5, + 0x0302ba6a, + 0x16ed5c82, + 0xcf8a2b1c, + 0x79a792b4, + 0x07f3f0f2, + 0x694ea1e2, + 0xda65cdf4, + 0x0506d5be, + 0x34d11f62, + 0xa6c48afe, + 0x2e349d53, + 0xf3a2a055, + 0x8a0532e1, + 0xf6a475eb, + 0x830b39ec, + 0x6040aaef, + 0x715e069f, + 0x6ebd5110, + 0x213ef98a, + 0xdd963d06, + 0x3eddae05, + 0xe64d46bd, + 0x5491b58d, + 0xc471055d, + 0x06046fd4, + 0x5060ff15, + 0x981924fb, + 0xbdd697e9, + 0x4089cc43, + 0xd967779e, + 0xe8b0bd42, + 0x8907888b, + 0x19e7385b, + 0xc879dbee, + 0x7ca1470a, + 0x427ce90f, + 0x84f8c91e, + 0x00000000, + 0x80098386, + 0x2b3248ed, + 0x111eac70, + 0x5a6c4e72, + 0x0efdfbff, + 0x850f5638, + 0xae3d1ed5, + 0x2d362739, + 0x0f0a64d9, + 0x5c6821a6, + 0x5b9bd154, + 0x36243a2e, + 0x0a0cb167, + 0x57930fe7, + 0xeeb4d296, + 0x9b1b9e91, + 0xc0804fc5, + 0xdc61a220, + 0x775a694b, + 0x121c161a, + 0x93e20aba, + 0xa0c0e52a, + 0x223c43e0, + 0x1b121d17, + 0x090e0b0d, + 0x8bf2adc7, + 0xb62db9a8, + 0x1e14c8a9, + 0xf1578519, + 0x75af4c07, + 0x99eebbdd, + 0x7fa3fd60, + 0x01f79f26, + 0x725cbcf5, + 0x6644c53b, + 0xfb5b347e, + 0x438b7629, + 0x23cbdcc6, + 0xedb668fc, + 0xe4b863f1, + 0x31d7cadc, + 0x63421085, + 0x97134022, + 0xc6842011, + 0x4a857d24, + 0xbbd2f83d, + 0xf9ae1132, + 0x29c76da1, + 0x9e1d4b2f, + 0xb2dcf330, + 0x860dec52, + 0xc177d0e3, + 0xb32b6c16, + 0x70a999b9, + 0x9411fa48, + 0xe9472264, + 0xfca8c48c, + 0xf0a01a3f, + 0x7d56d82c, + 0x3322ef90, + 0x4987c74e, + 0x38d9c1d1, + 0xca8cfea2, + 0xd498360b, + 0xf5a6cf81, + 0x7aa528de, + 0xb7da268e, + 0xad3fa4bf, + 0x3a2ce49d, + 0x78500d92, + 0x5f6a9bcc, + 0x7e546246, + 0x8df6c213, + 0xd890e8b8, + 0x392e5ef7, + 0xc382f5af, + 0x5d9fbe80, + 0xd0697c93, + 0xd56fa92d, + 0x25cfb312, + 0xacc83b99, + 0x1810a77d, + 0x9ce86e63, + 0x3bdb7bbb, + 0x26cd0978, + 0x596ef418, + 0x9aec01b7, + 0x4f83a89a, + 0x95e6656e, + 0xffaa7ee6, + 0xbc2108cf, + 0x15efe6e8, + 0xe7bad99b, + 0x6f4ace36, + 0x9fead409, + 0xb029d67c, + 0xa431afb2, + 0x3f2a3123, + 0xa5c63094, + 0xa235c066, + 0x4e7437bc, + 0x82fca6ca, + 0x90e0b0d0, + 0xa73315d8, + 0x04f14a98, + 0xec41f7da, + 0xcd7f0e50, + 0x91172ff6, + 0x4d768dd6, + 0xef434db0, + 0xaacc544d, + 0x96e4df04, + 0xd19ee3b5, + 0x6a4c1b88, + 0x2cc1b81f, + 0x65467f51, + 0x5e9d04ea, + 0x8c015d35, + 0x87fa7374, + 0x0bfb2e41, + 0x67b35a1d, + 0xdb9252d2, + 0x10e93356, + 0xd66d1347, + 0xd79a8c61, + 0xa1377a0c, + 0xf8598e14, + 0x13eb893c, + 0xa9ceee27, + 0x61b735c9, + 0x1ce1ede5, + 0x477a3cb1, + 0xd29c59df, + 0xf2553f73, + 0x141879ce, + 0xc773bf37, + 0xf753eacd, + 0xfd5f5baa, + 0x3ddf146f, + 0x447886db, + 0xafca81f3, + 0x68b93ec4, + 0x24382c34, + 0xa3c25f40, + 0x1d1672c3, + 0xe2bc0c25, + 0x3c288b49, + 0x0dff4195, + 0xa8397101, + 0x0c08deb3, + 0xb4d89ce4, + 0x566490c1, + 0xcb7b6184, + 0x32d570b6, + 0x6c48745c, + 0xb8d04257 + ]; + rinv3 = [ + 0x5150a7f4, + 0x7e536541, + 0x1ac3a417, + 0x3a965e27, + 0x3bcb6bab, + 0x1ff1459d, + 0xacab58fa, + 0x4b9303e3, + 0x2055fa30, + 0xadf66d76, + 0x889176cc, + 0xf5254c02, + 0x4ffcd7e5, + 0xc5d7cb2a, + 0x26804435, + 0xb58fa362, + 0xde495ab1, + 0x25671bba, + 0x45980eea, + 0x5de1c0fe, + 0xc302752f, + 0x8112f04c, + 0x8da39746, + 0x6bc6f9d3, + 0x03e75f8f, + 0x15959c92, + 0xbfeb7a6d, + 0x95da5952, + 0xd42d83be, + 0x58d32174, + 0x492969e0, + 0x8e44c8c9, + 0x756a89c2, + 0xf478798e, + 0x996b3e58, + 0x27dd71b9, + 0xbeb64fe1, + 0xf017ad88, + 0xc966ac20, + 0x7db43ace, + 0x63184adf, + 0xe582311a, + 0x97603351, + 0x62457f53, + 0xb1e07764, + 0xbb84ae6b, + 0xfe1ca081, + 0xf9942b08, + 0x70586848, + 0x8f19fd45, + 0x94876cde, + 0x52b7f87b, + 0xab23d373, + 0x72e2024b, + 0xe3578f1f, + 0x662aab55, + 0xb20728eb, + 0x2f03c2b5, + 0x869a7bc5, + 0xd3a50837, + 0x30f28728, + 0x23b2a5bf, + 0x02ba6a03, + 0xed5c8216, + 0x8a2b1ccf, + 0xa792b479, + 0xf3f0f207, + 0x4ea1e269, + 0x65cdf4da, + 0x06d5be05, + 0xd11f6234, + 0xc48afea6, + 0x349d532e, + 0xa2a055f3, + 0x0532e18a, + 0xa475ebf6, + 0x0b39ec83, + 0x40aaef60, + 0x5e069f71, + 0xbd51106e, + 0x3ef98a21, + 0x963d06dd, + 0xddae053e, + 0x4d46bde6, + 0x91b58d54, + 0x71055dc4, + 0x046fd406, + 0x60ff1550, + 0x1924fb98, + 0xd697e9bd, + 0x89cc4340, + 0x67779ed9, + 0xb0bd42e8, + 0x07888b89, + 0xe7385b19, + 0x79dbeec8, + 0xa1470a7c, + 0x7ce90f42, + 0xf8c91e84, + 0x00000000, + 0x09838680, + 0x3248ed2b, + 0x1eac7011, + 0x6c4e725a, + 0xfdfbff0e, + 0x0f563885, + 0x3d1ed5ae, + 0x3627392d, + 0x0a64d90f, + 0x6821a65c, + 0x9bd1545b, + 0x243a2e36, + 0x0cb1670a, + 0x930fe757, + 0xb4d296ee, + 0x1b9e919b, + 0x804fc5c0, + 0x61a220dc, + 0x5a694b77, + 0x1c161a12, + 0xe20aba93, + 0xc0e52aa0, + 0x3c43e022, + 0x121d171b, + 0x0e0b0d09, + 0xf2adc78b, + 0x2db9a8b6, + 0x14c8a91e, + 0x578519f1, + 0xaf4c0775, + 0xeebbdd99, + 0xa3fd607f, + 0xf79f2601, + 0x5cbcf572, + 0x44c53b66, + 0x5b347efb, + 0x8b762943, + 0xcbdcc623, + 0xb668fced, + 0xb863f1e4, + 0xd7cadc31, + 0x42108563, + 0x13402297, + 0x842011c6, + 0x857d244a, + 0xd2f83dbb, + 0xae1132f9, + 0xc76da129, + 0x1d4b2f9e, + 0xdcf330b2, + 0x0dec5286, + 0x77d0e3c1, + 0x2b6c16b3, + 0xa999b970, + 0x11fa4894, + 0x472264e9, + 0xa8c48cfc, + 0xa01a3ff0, + 0x56d82c7d, + 0x22ef9033, + 0x87c74e49, + 0xd9c1d138, + 0x8cfea2ca, + 0x98360bd4, + 0xa6cf81f5, + 0xa528de7a, + 0xda268eb7, + 0x3fa4bfad, + 0x2ce49d3a, + 0x500d9278, + 0x6a9bcc5f, + 0x5462467e, + 0xf6c2138d, + 0x90e8b8d8, + 0x2e5ef739, + 0x82f5afc3, + 0x9fbe805d, + 0x697c93d0, + 0x6fa92dd5, + 0xcfb31225, + 0xc83b99ac, + 0x10a77d18, + 0xe86e639c, + 0xdb7bbb3b, + 0xcd097826, + 0x6ef41859, + 0xec01b79a, + 0x83a89a4f, + 0xe6656e95, + 0xaa7ee6ff, + 0x2108cfbc, + 0xefe6e815, + 0xbad99be7, + 0x4ace366f, + 0xead4099f, + 0x29d67cb0, + 0x31afb2a4, + 0x2a31233f, + 0xc63094a5, + 0x35c066a2, + 0x7437bc4e, + 0xfca6ca82, + 0xe0b0d090, + 0x3315d8a7, + 0xf14a9804, + 0x41f7daec, + 0x7f0e50cd, + 0x172ff691, + 0x768dd64d, + 0x434db0ef, + 0xcc544daa, + 0xe4df0496, + 0x9ee3b5d1, + 0x4c1b886a, + 0xc1b81f2c, + 0x467f5165, + 0x9d04ea5e, + 0x015d358c, + 0xfa737487, + 0xfb2e410b, + 0xb35a1d67, + 0x9252d2db, + 0xe9335610, + 0x6d1347d6, + 0x9a8c61d7, + 0x377a0ca1, + 0x598e14f8, + 0xeb893c13, + 0xceee27a9, + 0xb735c961, + 0xe1ede51c, + 0x7a3cb147, + 0x9c59dfd2, + 0x553f73f2, + 0x1879ce14, + 0x73bf37c7, + 0x53eacdf7, + 0x5f5baafd, + 0xdf146f3d, + 0x7886db44, + 0xca81f3af, + 0xb93ec468, + 0x382c3424, + 0xc25f40a3, + 0x1672c31d, + 0xbc0c25e2, + 0x288b493c, + 0xff41950d, + 0x397101a8, + 0x08deb30c, + 0xd89ce4b4, + 0x6490c156, + 0x7b6184cb, + 0xd570b632, + 0x48745c6c, + 0xd04257b8 + ]; + } +} 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 new file mode 100644 index 000000000..c5caa50be --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/cipher_block_chaining_mode.dart @@ -0,0 +1,138 @@ +part of pdf; + +class _CipherBlockChainingMode { + //Constructor + _CipherBlockChainingMode(_AesEngine 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); + _isEncryption = false; + } + + //Fields + _AesEngine _cipher; + int _size; + List _bytes; + List _cbcBytes; + List _cbcNextBytes; + bool _isEncryption; + + //Fields + int get blockSize => _cipher.blockSize; + + //Implementation + 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 as _InvalidParameter)._parameters; + } + _reset(); + if (parameters != null) { + _cipher._initialize(_isEncryption, parameters); + } else if (oldEncryption != _isEncryption) { + throw ArgumentError.value(oldEncryption, + 'cannot change encrypting state without providing key.'); + } + } + + void _reset() { + _cbcBytes = List.from(_bytes); + _cbcNextBytes = List.filled(_size, 0, growable: true); + } + + Map _processBlock(List inputBytes, int inputOffset, + List outputBytes, int outputOffset) { + return _isEncryption + ? _encryptBlock(inputBytes, inputOffset, outputBytes, outputOffset) + : _decryptBlock(inputBytes, inputOffset, outputBytes, outputOffset); + } + + Map _encryptBlock(List inputBytes, int inputOffset, + List outputBytes, int outputOffset) { + if ((inputOffset + _size) > inputBytes.length) { + throw ArgumentError.value('Invalid length in input bytes'); + } + for (int i = 0; i < _size; i++) { + _cbcBytes[i] ^= inputBytes[inputOffset + i]; + } + final Map result = + _cipher._processBlock(_cbcBytes, 0, outputBytes, outputOffset); + outputBytes = result['output'] as List; + List.copyRange(_cbcBytes, 0, outputBytes, outputOffset, + outputOffset + _cbcBytes.length); + return result; + } + + Map _decryptBlock(List inputBytes, int inputOffset, + List outputBytes, int outputOffset) { + if ((inputOffset + _size) > inputBytes.length) { + throw ArgumentError.value('Invalid length in input bytes'); + } + List.copyRange( + _cbcNextBytes, 0, inputBytes, inputOffset, inputOffset + _size); + final Map result = _cipher._processBlock( + inputBytes, inputOffset, outputBytes, outputOffset); + outputBytes = result['output'] as List; + for (int i = 0; i < _size; i++) { + outputBytes[outputOffset + i] ^= _cbcBytes[i]; + } + final List tempBytes = _cbcBytes; + _cbcBytes = _cbcNextBytes; + _cbcNextBytes = tempBytes; + return {'length': result['length'], 'output': outputBytes}; + } +} + +class _InvalidParameter implements _ICipherParameter { + //Constructor + _InvalidParameter(_ICipherParameter parameter, List bytes, + [int offset, int length]) { + _parameters = parameter; + length ??= bytes.length; + offset ??= 0; + _bytes = List.filled(length, 0, growable: true); + List.copyRange(_bytes, 0, bytes, offset, offset + length); + } + + //Fields + _ICipherParameter _parameters; + List _bytes; + + //Properties + @override + List get keys => List.from(_bytes); + @override + set keys(List value) { + _bytes = value; + } +} + +class _KeyParameter implements _ICipherParameter { + //Constructor + _KeyParameter(List bytes) { + _bytes = List.from(bytes); + } + + //Fields + List _bytes; + + //Properties + @override + List get keys => List.from(_bytes); + @override + set keys(List value) { + _bytes = value; + } +} + +class _ICipherParameter { + List keys; +} 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 new file mode 100644 index 000000000..4c383ed35 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/enum.dart @@ -0,0 +1,61 @@ +part of pdf; + +/// Specifies the type of encryption algorithm. +enum PdfEncryptionAlgorithm { + /// RC4 encryption algorithm - 40-bit key. + rc4x40Bit, + + /// RC4 encryption algorithm - 128-bit key. + rc4x128Bit, + + /// AES encryption algorithm - 128-bit key. + aesx128Bit, + + /// AES encryption algorithm - 256-bit key. + aesx256Bit, + + /// AES encryption algorithm - 256-bit key with revision 6. + aesx256BitRevision6 +} + +/// Specifies the type of PDF permissions. +enum PdfPermissionsFlags { + /// Default value. + none, + + /// Print the document. + print, + + /// Edit content. + editContent, + + /// Copy content. + copyContent, + + /// Add or modify text annotations, fill in interactive form fields. + editAnnotations, + + /// Fill form fields. (Only for 128 bits key). + fillFields, + + /// Copy accessibility content. + accessibilityCopyContent, + + /// Assemble document permission. (Only for 128 bits key). + assembleDocument, + + /// Full quality print. + fullQualityPrint +} + +/// 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/pdf_encryptor.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_encryptor.dart new file mode 100644 index 000000000..251ce7a0f --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_encryptor.dart @@ -0,0 +1,1409 @@ +part of pdf; + +class _PdfEncryptor { + //constructor + _PdfEncryptor() { + _initialize(); + } + + //Fields + int _stringLength; + int _revisionNumber40Bit; + int _revisionNumber128Bit; + int _ownerLoopNum2; + int _ownerLoopNum; + List paddingBytes; + int _bytesAmount; + int _permissionSet; + int _permissionCleared; + int _permissionRevisionTwoMask; + int _revisionNumberOut; + int _versionNumberOut; + int _permissionValue; + List _randomBytes; + int _key40; + int _key128; + int _key256; + int _randomBytesAmount; + int _newKeyOffset; + + bool _encrypt; + bool _isChanged; + bool _hasComputedPasswordValues; + PdfEncryptionAlgorithm encryptionAlgorithm; + List _permissions; + int _revision; + String _userPassword; + String _ownerPassword; + List _ownerPasswordOut; + List _userPasswordOut; + List _encryptionKey; + int keyLength; + List customArray; + List _permissionFlagValues; + List _fileEncryptionKey; + List _userEncryptionKeyOut; + List _ownerEncryptionKeyOut; + List _permissionFlag; + List _userRandomBytes; + List _ownerRandomBytes; + bool _encryptMetadata; + bool _encryptOnlyAttachment; + + //Properties + + int get revisionNumber { + return _revision == 0 + ? (encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x40Bit + ? (_revisionNumberOut > 2 + ? _revisionNumberOut + : _revisionNumber40Bit) + : _revisionNumber128Bit) + : _revision; + } + + List get randomBytes { + if (_randomBytes == null) { + final Random random = Random.secure(); + _randomBytes = + List.generate(_randomBytesAmount, (i) => random.nextInt(256)); + } + return _randomBytes; + } + + List get permissions { + return _permissions; + } + + set permissions(List value) { + _isChanged = true; + _permissions = value; + _permissionValue = (_getPermissionValue(_permissions) | _permissionSet) & + _permissionCleared; + if (revisionNumber > 2) { + _permissionValue &= _permissionRevisionTwoMask; + } + _hasComputedPasswordValues = false; + } + + bool get encrypt { + final List perm = permissions; + final bool bEncrypt = + (perm.length == 1 && !perm.contains(PdfPermissionsFlags.none)) || + _userPassword.isNotEmpty || + _ownerPassword.isNotEmpty; + return !_encrypt ? false : bEncrypt; + } + + set encrypt(bool value) { + _encrypt = value; + } + + String get userPassword { + return _userPassword; + } + + set userPassword(String value) { + ArgumentError.checkNotNull(value); + if (_userPassword != value) { + _isChanged = true; + _userPassword = value; + _hasComputedPasswordValues = false; + } + } + + String get ownerPassword { + return _ownerPassword; + } + + set ownerPassword(String value) { + ArgumentError.checkNotNull(value); + if (_ownerPassword != value) { + _isChanged = true; + _ownerPassword = value; + _hasComputedPasswordValues = false; + } + } + + bool get encryptMetadata { + return _encryptMetadata; + } + + set encryptMetadata(bool value) { + ArgumentError.checkNotNull(value); + _hasComputedPasswordValues = false; + _encryptMetadata = value; + } + + List get ownerPasswordOut { + _initializeData(); + return _ownerPasswordOut; + } + + List get userPasswordOut { + _initializeData(); + return _userPasswordOut; + } + + _PdfArray get fileID { + final _PdfString str = _PdfString.fromBytes(randomBytes); + final _PdfArray array = _PdfArray(); + array._add(str); + array._add(str); + return array; + } + + //implementation + void _initialize() { + _key40 = 5; + _key128 = 16; + _key256 = 32; + _newKeyOffset = 5; + _userPassword = ''; + _ownerPassword = ''; + _stringLength = 32; + _revisionNumber40Bit = 2; + _revisionNumber128Bit = 3; + _revisionNumberOut = 0; + _versionNumberOut = 0; + _ownerLoopNum2 = 20; + _ownerLoopNum = 50; + _bytesAmount = 256; + _randomBytesAmount = 16; + keyLength = 0; + _encrypt = true; + _permissionSet = ~0x00f3f; + _permissionCleared = ~0x3; + _permissionRevisionTwoMask = 0xfff; + _revision = 0; + encryptionAlgorithm = PdfEncryptionAlgorithm.rc4x128Bit; + _permissionFlagValues = [ + 0x000000, + 0x000004, + 0x000008, + 0x000010, + 0x000020, + 0x000100, + 0x000200, + 0x000400, + 0x000800 + ]; + permissions = [PdfPermissionsFlags.none]; + paddingBytes = [ + 40, + 191, + 78, + 94, + 78, + 117, + 138, + 65, + 100, + 0, + 78, + 86, + 255, + 250, + 1, + 8, + 46, + 46, + 0, + 182, + 208, + 104, + 62, + 128, + 47, + 12, + 169, + 254, + 100, + 83, + 105, + 122 + ]; + customArray = List.filled(_bytesAmount, 0, growable: true); + _isChanged = false; + _hasComputedPasswordValues = false; + _encryptMetadata = true; + _encryptOnlyAttachment = false; + } + + void _initializeData() { + if (!_hasComputedPasswordValues) { + if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit) { + _userPasswordOut = _create256BitUserPassword(); + _ownerPasswordOut = _create256BitOwnerPassword(); + _createFileEncryptionKey(); + _userEncryptionKeyOut = _createUserEncryptionKey(); + _ownerEncryptionKeyOut = _createOwnerEncryptionKey(); + _permissionFlag = _createPermissionFlag(); + } else if (encryptionAlgorithm == + PdfEncryptionAlgorithm.aesx256BitRevision6) { + _createFileEncryptionKey(); + _createAcrobatX256BitUserPassword(); + _createAcrobatX256BitOwnerPassword(); + _permissionFlag = _createPermissionFlag(); + } else { + _ownerPasswordOut = _createOwnerPassword(); + _encryptionKey = _createEncryptionKey(userPassword, _ownerPasswordOut); + _userPasswordOut = _createUserPassword(); + } + _hasComputedPasswordValues = true; + } + } + + List _createUserPassword() { + return revisionNumber == 2 + ? _create40BitUserPassword() + : _create128BitUserPassword(); + } + + List _create40BitUserPassword() { + ArgumentError.checkNotNull(_encryptionKey); + return _encryptDataByCustom( + List.from(paddingBytes), _encryptionKey, _encryptionKey.length); + } + + List _create128BitUserPassword() { + ArgumentError.checkNotNull(_encryptionKey); + final List data = []; + data.addAll(paddingBytes); + data.addAll(randomBytes); + final List resultBytes = md5.convert(data).bytes; + final List dataForCustom = + List.generate(_randomBytesAmount, (i) => resultBytes[i]); + List dataFromCustom = _encryptDataByCustom( + dataForCustom, _encryptionKey, _encryptionKey.length); + for (int i = 1; i < _ownerLoopNum2; i++) { + final List currentKey = _getKeyWithOwnerPassword(_encryptionKey, i); + dataFromCustom = + _encryptDataByCustom(dataFromCustom, currentKey, currentKey.length); + } + return _padTrancateString(dataFromCustom); + } + + List _create256BitUserPassword() { + final Random random = Random.secure(); + _userRandomBytes = + List.generate(_randomBytesAmount, (i) => random.nextInt(256)); + final List userPasswordBytes = utf8.encode(_userPassword); + final List hash = []; + hash.addAll(userPasswordBytes); + hash.addAll(List.generate(8, (i) => _userRandomBytes[i])); + final List userPasswordOut = []; + userPasswordOut.addAll(sha256.convert(hash).bytes); + userPasswordOut.addAll(_userRandomBytes); + return userPasswordOut; + } + + List _createOwnerPassword() { + final String password = ownerPassword == null || ownerPassword.isEmpty + ? userPassword + : ownerPassword; + final List customKey = _getKeyFromOwnerPassword(password); + final List userPasswordBytes = + _padTrancateString(utf8.encode(userPassword)); + List dataFromCustom = + _encryptDataByCustom(userPasswordBytes, customKey, customKey.length); + if (revisionNumber > 2) { + for (int i = 1; i < _ownerLoopNum2; i++) { + final List currentKey = _getKeyWithOwnerPassword(customKey, i); + dataFromCustom = + _encryptDataByCustom(dataFromCustom, currentKey, currentKey.length); + } + } + return dataFromCustom; + } + + List _create256BitOwnerPassword() { + final Random random = Random.secure(); + _ownerRandomBytes = + List.generate(_randomBytesAmount, (i) => random.nextInt(256)); + final String password = ownerPassword == null || ownerPassword.isEmpty + ? userPassword + : ownerPassword; + final List ownerPasswordBytes = utf8.encode(password); + final List hash = []; + hash.addAll(ownerPasswordBytes); + hash.addAll(List.generate(8, (i) => _ownerRandomBytes[i])); + hash.addAll(_userPasswordOut); + final List ownerPasswordOut = []; + ownerPasswordOut.addAll(sha256.convert(hash).bytes); + ownerPasswordOut.addAll(_ownerRandomBytes); + return ownerPasswordOut; + } + + List _createUserEncryptionKey() { + final List hash = []; + hash.addAll(utf8.encode(userPassword)); + hash.addAll(List.generate(8, (i) => _userRandomBytes[i + 8])); + final List hashBytes = sha256.convert(hash).bytes; + return _AesCipherNoPadding(true, hashBytes) + ._processBlock(_fileEncryptionKey, 0, _fileEncryptionKey.length); + } + + List _createOwnerEncryptionKey() { + final String password = ownerPassword == null || ownerPassword.isEmpty + ? userPassword + : ownerPassword; + final List hash = []; + hash.addAll(utf8.encode(password)); + hash.addAll(List.generate(8, (i) => _ownerRandomBytes[i + 8])); + hash.addAll(_userPasswordOut); + final List hashBytes = sha256.convert(hash).bytes; + return _AesCipherNoPadding(true, hashBytes) + ._processBlock(_fileEncryptionKey, 0, _fileEncryptionKey.length); + } + + List _createPermissionFlag() { + final List permissionFlagBytes = [ + _permissionValue.toUnsigned(8).toInt(), + (_permissionValue >> 8).toUnsigned(8).toInt(), + (_permissionValue >> 16).toUnsigned(8).toInt(), + (_permissionValue >> 24).toUnsigned(8).toInt(), + 255, + 255, + 255, + 255, + 84, + 97, + 100, + 98, + 98, + 98, + 98, + 98 + ]; + return _AesCipherNoPadding(true, _fileEncryptionKey) + ._processBlock(permissionFlagBytes, 0, permissionFlagBytes.length); + } + + void _createAcrobatX256BitUserPassword() { + final List userPasswordBytes = utf8.encode(_userPassword); + final Random random = Random.secure(); + final List userValidationSalt = + List.generate(8, (i) => random.nextInt(256)); + final List userKeySalt = + List.generate(8, (i) => random.nextInt(256)); + List hash = []; + hash.addAll(userPasswordBytes); + hash.addAll(userValidationSalt); + hash = _acrobatXComputeHash(hash, userPasswordBytes, null); + _userPasswordOut = []; + _userPasswordOut.addAll(hash); + _userPasswordOut.addAll(userValidationSalt); + _userPasswordOut.addAll(userKeySalt); + hash = []; + hash.addAll(userPasswordBytes); + hash.addAll(userKeySalt); + hash = _acrobatXComputeHash(hash, userPasswordBytes, null); + _userEncryptionKeyOut = _AesCipherNoPadding(true, hash) + ._processBlock(_fileEncryptionKey, 0, _fileEncryptionKey.length); + } + + void _createAcrobatX256BitOwnerPassword() { + final String password = ownerPassword == null || ownerPassword.isEmpty + ? userPassword + : ownerPassword; + final List ownerPasswordBytes = utf8.encode(password); + final Random random = Random.secure(); + final List ownerValidationSalt = + List.generate(8, (i) => random.nextInt(256)); + final List ownerKeySalt = + List.generate(8, (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(ownerValidationSalt); + _ownerPasswordOut.addAll(ownerKeySalt); + List hash = []; + hash.addAll(ownerPasswordBytes); + hash.addAll(ownerKeySalt); + hash.addAll(_userPasswordOut); + hash = _acrobatXComputeHash(hash, ownerPasswordBytes, _userPasswordOut); + _ownerEncryptionKeyOut = _AesCipherNoPadding(true, hash) + ._processBlock(_fileEncryptionKey, 0, _fileEncryptionKey.length); + } + + void _createFileEncryptionKey() { + final Random random = Random.secure(); + _fileEncryptionKey = List.generate(32, (i) => random.nextInt(256)); + } + + List _encryptDataByCustom(List data, List key, int keyLength) { + final List buffer = List.filled(data.length, 0, growable: true); + _recreateCustomArray(key, keyLength); + keyLength = data.length; + int tmp1 = 0; + int tmp2 = 0; + for (int i = 0; i < keyLength; i++) { + tmp1 = (tmp1 + 1) % _bytesAmount; + tmp2 = (tmp2 + customArray[tmp1]) % _bytesAmount; + final int temp = customArray[tmp1]; + customArray[tmp1] = customArray[tmp2]; + customArray[tmp2] = temp; + final int byteXor = + customArray[(customArray[tmp1] + customArray[tmp2]) % _bytesAmount]; + buffer[i] = (data[i] ^ byteXor).toUnsigned(8).toInt(); + } + return buffer; + } + + void _recreateCustomArray(List key, int keyLength) { + final List tempArray = + List.filled(_bytesAmount, 0, growable: true); + for (int i = 0; i < _bytesAmount; i++) { + tempArray[i] = key[i % keyLength]; + customArray[i] = i.toUnsigned(8).toInt(); + } + int temp = 0; + for (int i = 0; i < _bytesAmount; i++) { + temp = (temp + customArray[i] + tempArray[i]) % _bytesAmount; + final int tempByte = customArray[i]; + customArray[i] = customArray[temp]; + customArray[temp] = tempByte; + } + } + + List _getKeyFromOwnerPassword(String password) { + final List passwordBytes = _padTrancateString(utf8.encode(password)); + List currentHash = md5.convert(passwordBytes).bytes; + final int length = _getKeyLength(); + if (revisionNumber > 2) { + for (int i = 0; i < _ownerLoopNum; i++) { + currentHash = md5 + .convert(currentHash.length == length + ? currentHash + : List.generate(length, (i) => currentHash[i])) + .bytes; + } + } + return currentHash.length == length + ? currentHash + : List.generate(length, (i) => currentHash[i]); + } + + int _getKeyLength() { + return keyLength != 0 + ? (keyLength ~/ 8) + : (encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x40Bit + ? _key40 + : ((encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit) + ? _key128 + : _key256)); + } + + List _padTrancateString(List source) { + ArgumentError.checkNotNull(source); + final List passwordBytes = []; + if (source.isNotEmpty) { + passwordBytes.addAll(source); + } + if (source.length < _stringLength) { + passwordBytes.addAll( + paddingBytes.getRange(0, paddingBytes.length - passwordBytes.length)); + } + return List.generate(_stringLength, (i) => passwordBytes[i]); + } + + List _getKeyWithOwnerPassword(List originalKey, int index) { + ArgumentError.checkNotNull(originalKey); + final List result = + List.filled(originalKey.length, 0, growable: true); + for (int i = 0; i < originalKey.length; i++) { + result[i] = (originalKey[i] ^ index).toUnsigned(8).toInt(); + } + return result; + } + + List _createEncryptionKey( + String inputPassword, List ownerPasswordBytes) { + ArgumentError.checkNotNull(inputPassword); + ArgumentError.checkNotNull(ownerPasswordBytes); + final List passwordBytes = + _padTrancateString(utf8.encode(inputPassword)); + final List encryptionKeyData = []; + encryptionKeyData.addAll(passwordBytes); + encryptionKeyData.addAll(ownerPasswordBytes); + encryptionKeyData.addAll([ + _permissionValue.toUnsigned(8).toInt(), + (_permissionValue >> 8).toUnsigned(8).toInt(), + (_permissionValue >> 16).toUnsigned(8).toInt(), + (_permissionValue >> 24).toUnsigned(8).toInt() + ]); + encryptionKeyData.addAll(randomBytes); + //if (revisionNumber > 3 && !EncryptMetaData) then add 4 default bytes + List currentHash = md5.convert(encryptionKeyData).bytes; + final int length = _getKeyLength(); + if (revisionNumber > 2) { + for (int i = 0; i < _ownerLoopNum; i++) { + currentHash = md5 + .convert(currentHash.length == length + ? currentHash + : List.generate(length, (i) => currentHash[i])) + .bytes; + } + } + return currentHash.length == length + ? currentHash + : List.generate(length, (i) => currentHash[i]); + } + + List _acrobatXComputeHash( + 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++) { + final List roundHash = List.filled( + (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); + position += password.length; + List.copyRange(roundHash, position, hash, 0, hash.length); + position += hash.length; + if (key != null && key.length >= 48) { + List.copyRange(roundHash, position, key, 0, 48); + position += 48; + } + } + final List hashFirst = List.generate(16, (i) => hash[i]); + final List hashSecond = List.generate(16, (i) => hash[i + 16]); + final _AesCipher encrypt = _AesCipher(true, hashFirst, hashSecond); + finalHashKey = encrypt._update(roundHash, 0, roundHash.length); + final List finalHashKeyFirst = + List.generate(16, (i) => finalHashKey[i]); + final BigInt finalKeyBigInteger = + _readBigIntFromBytes(finalHashKeyFirst, 0, finalHashKeyFirst.length); + final BigInt divisior = BigInt.parse('3'); + final BigInt algorithmNumber = finalKeyBigInteger % divisior; + final int algorithmIndex = algorithmNumber.toInt(); + if (algorithmIndex == 0) { + hash = sha256.convert(finalHashKey).bytes; + } else if (algorithmIndex == 1) { + hash = sha384.convert(finalHashKey).bytes; + } else { + hash = sha512.convert(finalHashKey).bytes; + } + } + return (hash.length > 32) ? List.generate(32, (i) => hash[i]) : hash; + } + + BigInt _readBigIntFromBytes(List bytes, int start, int end) { + if (end - start <= 4) { + int result = 0; + for (int i = end - 1; i >= start; i--) { + result = result * 256 + bytes[i]; + } + return BigInt.from(result); + } + final int mid = start + ((end - start) >> 1); + return (_readBigIntFromBytes(bytes, start, mid) + + _readBigIntFromBytes(bytes, mid, end) * + (BigInt.one << ((mid - start) * 8))); + } + + void _readFromDictionary(_PdfDictionary dictionary) { + ArgumentError.checkNotNull(dictionary); + _IPdfPrimitive obj; + if (dictionary.containsKey(_DictionaryProperties.filter)) { + obj = + _PdfCrossTable._dereference(dictionary[_DictionaryProperties.filter]); + } + if (obj != null && + obj is _PdfName && + obj._name != _DictionaryProperties.standard) { + throw ArgumentError.value( + obj, 'Invalid Format: Unsupported security filter'); + } + _permissionValue = dictionary._getInt(_DictionaryProperties.p); + _updatePermissions(_permissionValue & ~_permissionSet); + _versionNumberOut = dictionary._getInt(_DictionaryProperties.v); + _revisionNumberOut = dictionary._getInt(_DictionaryProperties.r); + if (_revisionNumberOut != null) { + _revision = _revisionNumberOut; + } + int keySize = dictionary._getInt(_DictionaryProperties.v); + if (keySize == 4 && keySize != _revisionNumberOut) { + throw ArgumentError.value( + 'Invalid Format: V and R entries of the Encryption dictionary does not match.'); + } + if (keySize == 5) { + _userEncryptionKeyOut = + dictionary._getString(_DictionaryProperties.ue).data; + _ownerEncryptionKeyOut = + dictionary._getString(_DictionaryProperties.oe).data; + _permissionFlag = dictionary._getString(_DictionaryProperties.perms).data; + } + _userPasswordOut = dictionary._getString(_DictionaryProperties.u).data; + _ownerPasswordOut = dictionary._getString(_DictionaryProperties.o).data; + keyLength = dictionary.containsKey(_DictionaryProperties.length) + ? dictionary._getInt(_DictionaryProperties.length) + : (keySize == 1 ? 40 : (keySize == 2 ? 128 : 256)); + if (keyLength == 128 && _revisionNumberOut < 4) { + keySize = 2; + encryptionAlgorithm = PdfEncryptionAlgorithm.rc4x128Bit; + } else if ((keyLength == 128 || keyLength == 256) && + _revisionNumberOut >= 4) { + final _PdfDictionary cryptFilter = + dictionary[_DictionaryProperties.cf] as _PdfDictionary; + final _PdfDictionary standardCryptFilter = + cryptFilter[_DictionaryProperties.stdCF] as _PdfDictionary; + final String filterName = + (standardCryptFilter[_DictionaryProperties.cfm] as _PdfName)._name; + if (keyLength == 128) { + keySize = 2; + encryptionAlgorithm = filterName != 'V2' + ? PdfEncryptionAlgorithm.aesx128Bit + : PdfEncryptionAlgorithm.rc4x128Bit; + } else { + keySize = 3; + encryptionAlgorithm = PdfEncryptionAlgorithm.aesx256Bit; + } + } else if (keyLength == 40) { + keySize = 1; + encryptionAlgorithm = PdfEncryptionAlgorithm.rc4x40Bit; + } else if (keyLength <= 128 && + keyLength > 40 && + keyLength % 8 == 0 && + _revisionNumberOut < 4) { + encryptionAlgorithm = PdfEncryptionAlgorithm.rc4x128Bit; + keySize = 2; + } else { + encryptionAlgorithm = PdfEncryptionAlgorithm.aesx256Bit; + keySize = 3; + } + if (_revisionNumberOut == 6) { + encryptionAlgorithm = PdfEncryptionAlgorithm.aesx256BitRevision6; + keySize = 4; + } + if (keyLength != 0 && + keyLength % 8 != 0 && + (keySize == 1 || keySize == 2 || keySize == 3)) { + throw ArgumentError.value( + 'Invalid format: Invalid/Unsupported security dictionary.'); + } + _hasComputedPasswordValues = true; + } + + void _updatePermissions(int value) { + _permissions = []; + if (value & 0x000004 > 0) { + _permissions.add(PdfPermissionsFlags.print); + } + if (value & 0x000008 > 0) { + _permissions.add(PdfPermissionsFlags.editContent); + } + if (value & 0x000010 > 0) { + _permissions.add(PdfPermissionsFlags.copyContent); + } + if (value & 0x000020 > 0) { + _permissions.add(PdfPermissionsFlags.editAnnotations); + } + if (value & 0x000100 > 0) { + _permissions.add(PdfPermissionsFlags.fillFields); + } + if (value & 0x000200 > 0) { + _permissions.add(PdfPermissionsFlags.accessibilityCopyContent); + } + if (value & 0x000400 > 0) { + _permissions.add(PdfPermissionsFlags.assembleDocument); + } + if (value & 0x000800 > 0) { + _permissions.add(PdfPermissionsFlags.fullQualityPrint); + } + if (permissions.isEmpty) { + _permissions.add(PdfPermissionsFlags.none); + } + } + + bool _checkPassword(String password, _PdfString key) { + ArgumentError.checkNotNull(password); + ArgumentError.checkNotNull(key); + bool result = false; + final List fileId = + _randomBytes != null ? List.from(_randomBytes) : _randomBytes; + _randomBytes = List.from(key.data); + if (_authenticateOwnerPassword(password)) { + _ownerPassword = password; + result = true; + } else if (_authenticateUserPassword(password)) { + _userPassword = password; + result = true; + } else { + _encryptionKey = null; + } + if (!result) { + _randomBytes = fileId; + } + return result; + } + + bool _authenticateUserPassword(String password) { + if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { + return _authenticate256BitUserPassword(password); + } else { + _encryptionKey = _createEncryptionKey(password, _ownerPasswordOut); + return _compareByteArrays(_createUserPassword(), _userPasswordOut, + revisionNumber == 2 ? null : 0x10); + } + } + + bool _authenticateOwnerPassword(String password) { + if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { + return _authenticate256BitOwnerPassword(password); + } else { + _encryptionKey = _getKeyFromOwnerPassword(password); + List buff = _ownerPasswordOut; + if (revisionNumber == 2) { + 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)); + buff = _encryptDataByCustom(buff, currKey, currKey.length); + } + } + _encryptionKey = null; + final String userPassword = _convertToPassword(buff); + if (_authenticateUserPassword(userPassword)) { + _userPassword = userPassword; + _ownerPassword = password; + return true; + } else { + return false; + } + } + } + + String _convertToPassword(List array) { + String result; + int length = array.length; + for (int i = 0; i < length; ++i) { + if (array[i] == paddingBytes[0]) { + if (i < length - 1 && array[i + 1] == paddingBytes[1]) { + length = i; + break; + } + } + } + result = _PdfString._byteToString(array, length); + return result; + } + + bool _authenticate256BitUserPassword(String password) { + final List userValidationSalt = List.filled(8, 0, growable: true); + final List userKeySalt = List.filled(8, 0, growable: true); + final List hashProvided = List.filled(32, 0, growable: true); + _userRandomBytes = List.filled(16, 0, growable: true); + List hash; + final List userPassword = utf8.encode(password); + if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { + List.copyRange(hashProvided, 0, _userPasswordOut, 0, 32); + List.copyRange(userValidationSalt, 0, _userPasswordOut, 32, 40); + final List combinedUserpassword = List.filled( + userPassword.length + userValidationSalt.length, 0, + growable: true); + List.copyRange( + 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); + 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); + hash = List.filled( + userPassword.length + userValidationSalt.length, 0, + growable: true); + List.copyRange(hash, 0, userPassword, 0, userPassword.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) { + int i = 0; + while ((i < hashFound.length) && (hashFound[i] == hashProvided[i])) { + i += 1; + } + if (i == hashFound.length) { + bEqual = true; + } + } + if (bEqual) { + _findFileEncryptionKey(password); + } + return bEqual; + } + } + + bool _authenticate256BitOwnerPassword(String password) { + 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); + List hash; + bool oEqual = false; + final List ownerPassword = utf8.encode(password); + if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { + List.copyRange(hashProvided, 0, _ownerPasswordOut, 0, 32); + List.copyRange(ownerValidationSalt, 0, _ownerPasswordOut, 32, 40); + int userKeyLength = 48; + if (_userPasswordOut.length < 48) userKeyLength = _userPasswordOut.length; + final List mixedOwnerPassword = List.filled( + 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); + List.copyRange( + mixedOwnerPassword, + ownerPassword.length + ownerValidationSalt.length, + _userPasswordOut, + 0, + userKeyLength); + hash = _acrobatXComputeHash( + mixedOwnerPassword, ownerPassword, _userPasswordOut); + _acrobatXOwnerFileEncryptionKey(password); + oEqual = _compareByteArrays(hash, hashProvided); + if (oEqual == true) { + final List ownerRandom = _fileEncryptionKey; + final String userPassword = password; + _ownerRandomBytes = null; + if (_authenticateUserPassword(userPassword)) { + _userPassword = userPassword; + _ownerPassword = password; + } else { + _fileEncryptionKey = ownerRandom; + } + } else { + _ownerRandomBytes = null; + } + return oEqual; + } else { + final List userPasswordOut = List.filled(48, 0, growable: true); + List.copyRange(userPasswordOut, 0, _userPasswordOut, 0, 48); + List.copyRange( + hashProvided, 0, _ownerPasswordOut, 0, hashProvided.length); + List.copyRange(_ownerRandomBytes, 0, _ownerPasswordOut, 32, 48); + List.copyRange(ownerValidationSalt, 0, _ownerRandomBytes, 0, + ownerValidationSalt.length); + List.copyRange(ownerKeySalt, 0, _ownerRandomBytes, + ownerValidationSalt.length, ownerKeySalt.length); + hash = List.filled( + 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); + final List hashFound = sha256.convert(hash).bytes; + bool bEqual = false; + if (hashFound.length == hashProvided.length) { + int i = 0; + while ((i < hashFound.length) && (hashFound[i] == hashProvided[i])) { + i += 1; + } + if (i == hashFound.length) { + bEqual = true; + } + } + _findFileEncryptionKey(password); + if (bEqual == true) { + _ownerRandomBytes = null; + final String userPassword = password; + if (_authenticateUserPassword(userPassword)) { + _userPassword = userPassword; + _ownerPassword = password; + } + } else { + _ownerRandomBytes = null; + } + return bEqual; + } + } + + void _findFileEncryptionKey(String password) { + List hash; + List hashFound; + List forDecryption; + if (_ownerRandomBytes != null) { + final List ownerValidationSalt = + List.filled(8, 0, growable: true); + final List ownerKeySalt = List.filled(8, 0, growable: true); + final List ownerPassword = utf8.encode(password); + final List userPasswordOut = List.filled(48, 0, growable: true); + List.copyRange(userPasswordOut, 0, _userPasswordOut, 0, 48); + List.copyRange(ownerValidationSalt, 0, _ownerRandomBytes, 0, 8); + List.copyRange(ownerKeySalt, 0, _ownerRandomBytes, 8, 16); + hash = List.filled( + 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); + hashFound = sha256.convert(hash).bytes; + forDecryption = _ownerEncryptionKeyOut; + } else if (_userRandomBytes != null) { + 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); + List.copyRange(hash, 0, userPassword, 0, userPassword.length); + List.copyRange( + hash, userPassword.length, userKeySalt, 0, userKeySalt.length); + hashFound = sha256.convert(hash).bytes; + forDecryption = _userEncryptionKeyOut; + } + _fileEncryptionKey = _AesCipherNoPadding(false, hashFound) + ._processBlock(forDecryption, 0, forDecryption.length); + } + + bool _compareByteArrays(List array1, List array2, [int size]) { + bool result = true; + if (size == null) { + if (array1 == null || array2 == null) { + result = (array1 == array2); + } else if (array1.length != array2.length) { + result = false; + } else { + for (int i = 0; i < array1.length; ++i) { + if (array1[i] != array2[i]) { + result = false; + break; + } + } + } + } else { + if (array1 == null || array2 == null) { + 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.'); + } else if (array1.length != array2.length) { + result = false; + } else { + for (int i = 0; i < size; ++i) { + if (array1[i] != array2[i]) { + result = false; + break; + } + } + } + } + return result; + } + + void _acrobatXOwnerFileEncryptionKey(String password) { + 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; + if (_userPasswordOut.length < 48) { + userKeyLength = _userPasswordOut.length; + } + final List combinedPassword = List.filled( + 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); + final List fileEncryptionKey = List.from(_ownerEncryptionKeyOut); + _fileEncryptionKey = _AesCipherNoPadding(false, hash) + ._processBlock(fileEncryptionKey, 0, fileEncryptionKey.length); + } + + void _advanceXUserFileEncryptionKey(String password) { + final List userKeySalt = List.filled(8, 0, growable: true); + List.copyRange(userKeySalt, 0, _userPasswordOut, 40, 48); + final List userpassword = utf8.encode(password); + final List combinedUserPassword = List.filled( + 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); + 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, (i) => random.nextInt(256)); + } + + _PdfDictionary _saveToDictionary(_PdfDictionary dictionary) { + if (_isChanged) { + _revisionNumberOut = 0; + _versionNumberOut = 0; + _revision = 0; + keyLength = 0; + } + dictionary[_DictionaryProperties.filter] = + _PdfName(_DictionaryProperties.standard); + dictionary[_DictionaryProperties.p] = _PdfNumber(_permissionValue); + dictionary[_DictionaryProperties.u] = _PdfString.fromBytes(userPasswordOut); + dictionary[_DictionaryProperties.o] = + _PdfString.fromBytes(ownerPasswordOut); + if (dictionary.containsKey(_DictionaryProperties.length)) { + keyLength = 0; + } + dictionary[_DictionaryProperties.length] = _PdfNumber(_getKeyLength() * 8); + final bool isAes4Dict = false; + if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { + dictionary[_DictionaryProperties.r] = _PdfNumber( + (_revisionNumberOut > 0 && isAes4Dict) + ? _revisionNumberOut + : (_getKeySize() + 3)); + dictionary[_DictionaryProperties.v] = _PdfNumber( + (_versionNumberOut > 0 && isAes4Dict) + ? _versionNumberOut + : (_getKeySize() + 3)); + if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { + dictionary[_DictionaryProperties.v] = _PdfNumber(5); + dictionary[_DictionaryProperties.r] = _PdfNumber(6); + } else if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit) { + dictionary[_DictionaryProperties.v] = _PdfNumber(5); + dictionary[_DictionaryProperties.r] = _PdfNumber(5); + } + dictionary[_DictionaryProperties.stmF] = + _PdfName(_DictionaryProperties.stdCF); + dictionary[_DictionaryProperties.strF] = + _PdfName(_DictionaryProperties.stdCF); + dictionary[_DictionaryProperties.cf] = _getCryptFilterDictionary(); + if (!_encryptMetadata) { + if (!dictionary.containsKey(_DictionaryProperties.encryptMetadata)) { + dictionary[_DictionaryProperties.encryptMetadata] = + _PdfBoolean(_encryptMetadata); + } + } + if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { + dictionary[_DictionaryProperties.ue] = + _PdfString.fromBytes(_userEncryptionKeyOut); + dictionary[_DictionaryProperties.oe] = + _PdfString.fromBytes(_ownerEncryptionKeyOut); + dictionary[_DictionaryProperties.perms] = + _PdfString.fromBytes(_permissionFlag); + } + } else { + dictionary[_DictionaryProperties.r] = _PdfNumber( + (_revisionNumberOut > 0 && !isAes4Dict) + ? _revisionNumberOut + : (_getKeySize() + 2).toInt()); + dictionary[_DictionaryProperties.v] = _PdfNumber( + (_versionNumberOut > 0 && !isAes4Dict) + ? _versionNumberOut + : (_getKeySize() + 1).toInt()); + } + dictionary._archive = false; + return dictionary; + } + + _PdfDictionary _getCryptFilterDictionary() { + final _PdfDictionary standardCryptFilter = _PdfDictionary(); + standardCryptFilter[_DictionaryProperties.cfm] = _PdfName( + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit + ? _DictionaryProperties.aesv3 + : _DictionaryProperties.aesv2); + standardCryptFilter[_DictionaryProperties.authEvent] = + _PdfName(_DictionaryProperties.docOpen); + standardCryptFilter[_DictionaryProperties.length] = _PdfNumber( + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit + ? _key256 + : ((encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit) + ? _key128 + : 128)); + final _PdfDictionary cryptFilterDictionary = _PdfDictionary(); + cryptFilterDictionary[_DictionaryProperties.stdCF] = standardCryptFilter; + return cryptFilterDictionary; + } + + int _getPermissionValue(List permissionFlags) { + int defaultValue = 0; + permissionFlags.forEach((PdfPermissionsFlags flag) { + defaultValue |= _permissionFlagValues[flag.index]; + }); + return defaultValue; + } + + int _getKeySize() { + int result; + switch (encryptionAlgorithm) { + case PdfEncryptionAlgorithm.rc4x40Bit: + result = 0; + break; + case PdfEncryptionAlgorithm.aesx128Bit: + result = 1; + break; + case PdfEncryptionAlgorithm.aesx256Bit: + result = 2; + break; + case PdfEncryptionAlgorithm.aesx256BitRevision6: + result = 3; + break; + default: + result = 1; + break; + } + return result; + } + + List _encryptData( + int currentObjectNumber, List data, bool isEncryption) { + ArgumentError.checkNotNull(data, 'data value cannot be null'); + if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { + return isEncryption + ? _aesEncrypt(data, _fileEncryptionKey) + : _aesDecrypt(data, _fileEncryptionKey); + } + _initializeData(); + final int genNumber = 0; + int keyLen = 0; + List newKey; + if (_encryptionKey.length == 5) { + newKey = List.filled(_encryptionKey.length + _newKeyOffset, 0, + growable: true); + for (int i = 0; i < _encryptionKey.length; ++i) { + newKey[i] = _encryptionKey[i]; + } + int j = _encryptionKey.length - 1; + newKey[++j] = currentObjectNumber.toUnsigned(8); + newKey[++j] = (currentObjectNumber >> 8).toUnsigned(8); + newKey[++j] = (currentObjectNumber >> 16).toUnsigned(8); + newKey[++j] = genNumber.toUnsigned(8); + newKey[++j] = (genNumber >> 8).toUnsigned(8); + keyLen = newKey.length; + newKey = _prepareKeyForEncryption(newKey); + } else { + newKey = List.filled( + _encryptionKey.length + + ((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); + newKey[++j] = (currentObjectNumber >> 8).toUnsigned(8); + newKey[++j] = (currentObjectNumber >> 16).toUnsigned(8); + newKey[++j] = genNumber.toUnsigned(8); + newKey[++j] = (genNumber >> 8).toUnsigned(8); + if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit) { + newKey[++j] = (0x73).toUnsigned(8); + newKey[++j] = (0x41).toUnsigned(8); + newKey[++j] = (0x6c).toUnsigned(8); + newKey[++j] = (0x54).toUnsigned(8); + } + newKey = md5.convert(newKey).bytes; + keyLen = newKey.length; + } + keyLen = min(keyLen, newKey.length); + if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit) { + return isEncryption + ? _aesEncrypt(data, newKey) + : _aesDecrypt(data, newKey); + } + return _encryptDataByCustom(data, newKey, keyLen); + } + + 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; + } + + 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 { + return data; + } + return result; + } + + List _prepareKeyForEncryption(List originalKey) { + ArgumentError.checkNotNull(originalKey, 'Value cannot be null'); + final int keyLen = originalKey.length; + final List newKey = md5.convert(originalKey).bytes; + if (keyLen > _randomBytesAmount) { + 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 { + return newKey; + } + } + + _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(_key256) + .._newKeyOffset = _cloneInt(_key256) + .._encrypt = _cloneBool(_encrypt) + .._isChanged = _cloneBool(_isChanged) + .._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) + .._encryptMetadata = _cloneBool(_encryptMetadata) + .._encryptOnlyAttachment = _cloneBool(_encryptOnlyAttachment) + ..encryptionAlgorithm = encryptionAlgorithm + .._userPassword = _userPassword + .._ownerPassword = _ownerPassword; + encryptor._permissions = _permissions != null + ? List.generate(_permissions.length, (i) => _permissions[i]) + : null; + return encryptor; + } + + bool _cloneBool(bool value) { + if (value != null) { + if (value) { + return true; + } else { + return false; + } + } else { + return null; + } + } + + int _cloneInt(int value) { + if (value != null) { + return value.toInt(); + } else { + return null; + } + } + + List _cloneList(List value) { + if (value != null) { + return List.generate(value.length, (i) => value[i], growable: true); + } 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 new file mode 100644 index 000000000..985d3580c --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_security.dart @@ -0,0 +1,443 @@ +part of pdf; + +/// Represents the security settings of the PDF document. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Document security +/// PdfSecurity security = document.security; +/// //Set security options +/// security.algorithm = PdfEncryptionAlgorithm.rc4x128Bit; +/// security.userPassword = 'password'; +/// security.ownerPassword = 'syncfusion'; +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` +class PdfSecurity { + //constructor + /// Initializes a new instance of the [PdfSecurity] class. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Document security + /// PdfSecurity security = document.security; + /// //Set security options + /// security.algorithm = PdfEncryptionAlgorithm.rc4x128Bit; + /// security.userPassword = 'password'; + /// security.ownerPassword = 'syncfusion'; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfSecurity._() { + _initialize(); + } + + //Fields + _PdfEncryptor _encryptor; + PdfPermissions _permissions; + // ignore: prefer_final_fields + bool _conformance = false; + bool _modifiedSecurity = false; + + /// Gets the type of encryption algorithm used. + /// + /// ```dart + /// //Load an exisiting PDF document. + /// PdfDocument document = PdfDocument.fromBase64String(pdfData, 'password'); + /// //Get encryption algorithm + /// PdfEncryptionAlgorithm algorithm = document.security.algorithm; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfEncryptionAlgorithm get algorithm { + return _encryptor.encryptionAlgorithm; + } + + /// Sets the type of encryption algorithm. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Document security + /// PdfSecurity security = document.security; + /// //Set security options + /// security.algorithm = PdfEncryptionAlgorithm.rc4x128Bit; + /// security.userPassword = 'password'; + /// security.ownerPassword = 'syncfusion'; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + set algorithm(PdfEncryptionAlgorithm value) { + ArgumentError.checkNotNull(value); + _encryptor.encryptionAlgorithm = value; + _encryptor.encrypt = true; + _encryptor._isChanged = true; + _encryptor._hasComputedPasswordValues = false; + _modifiedSecurity = true; + } + + /// Gets the owner password. + /// + /// If the PDF document is password protected you can use the owner password + /// to open the document and change its permissions. + /// + /// ```dart + /// //Load an exisiting PDF document. + /// PdfDocument document = PdfDocument.fromBase64String(pdfData, 'password'); + /// //Get the owner password. + /// String ownerPassword = document.security.ownerPassword; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + String get ownerPassword { + return _encryptor.ownerPassword; + } + + /// Sets the owner password. + /// + /// If the PDF document is password protected you can use the owner password + /// to open the document and change its permissions. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Document security + /// PdfSecurity security = document.security; + /// //Set security options + /// security.algorithm = PdfEncryptionAlgorithm.rc4x128Bit; + /// security.userPassword = 'password'; + /// security.ownerPassword = 'syncfusion'; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + set ownerPassword(String value) { + if (_conformance) { + throw ArgumentError( + 'Document encryption is not allowed with Conformance documents.'); + } + _encryptor.ownerPassword = value; + _encryptor.encrypt = true; + _modifiedSecurity = true; + } + + /// Gets the user password. + /// + /// User password is required when the PDF document is opened in a viewer. + /// + /// ```dart + /// //Load an exisiting PDF document. + /// PdfDocument document = PdfDocument.fromBase64String(pdfData, 'password'); + /// //Get the user password. + /// String userPassword = document.security.userPassword; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + String get userPassword { + return _encryptor.userPassword; + } + + /// Sets the user password. + /// + /// User password is required when the PDF document is opened in a viewer. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Document security + /// PdfSecurity security = document.security; + /// //Set security options + /// security.algorithm = PdfEncryptionAlgorithm.rc4x128Bit; + /// security.userPassword = 'password'; + /// security.ownerPassword = 'syncfusion'; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + set userPassword(String value) { + if (_conformance) { + throw ArgumentError( + 'Document encryption is not allowed with Conformance documents.'); + } + _encryptor.userPassword = value; + _encryptor.encrypt = true; + _modifiedSecurity = true; + } + + /// Gets the permissions when the document is opened with user password. + /// + /// We can add or remove permissions flags by using add() and remove() method + /// in [PdfPermissions] class. + /// + /// ```dart + /// //Load an exisiting PDF document. + /// PdfDocument document = PdfDocument.fromBase64String(pdfData, 'password'); + /// //Get the permissions. + /// PdfPermissions pdfPermissions = document.security.permissions; + /// //Add permissions + /// permissions.add([PdfPermissionsFlags.editContent, PdfPermissionsFlags.copyContent]); + /// //Remove permissions + /// permissions.remove([PdfPermissionsFlags.editContent, PdfPermissionsFlags.copyContent]); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfPermissions get permissions { + _permissions ??= PdfPermissions._(_encryptor, _encryptor.permissions); + return _permissions; + } + + //Implementation + void _initialize() { + _encryptor = _PdfEncryptor(); + } +} + +/// Represents the permissions of the PDF document. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Document security +/// PdfSecurity security = document.security; +/// //Set security options +/// security.algorithm = PdfEncryptionAlgorithm.rc4x128Bit; +/// security.userPassword = 'password'; +/// security.ownerPassword = 'syncfusion'; +/// //Get PDF permission. +/// PdfPermissions permissions = security.permissions; +/// //Add permissions. +/// permissions.add([PdfPermissionsFlags.print]); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` +class PdfPermissions { + //constructor + PdfPermissions._( + _PdfEncryptor encryptor, List permissions) { + ArgumentError.checkNotNull(encryptor); + ArgumentError.checkNotNull(permissions); + _encryptor = encryptor; + _permissions = permissions; + } + + //Fields + _PdfEncryptor _encryptor; + List _permissions; + bool _modifiedPermissions = false; + + //Implementation + /// Get the permissions. + /// + /// ```dart + /// //Load encrypted PDF document with password. + /// PdfDocument document = PdfDocument(inputBytes: pdfData, password: 'password'); + /// //Document security + /// PdfSecurity security = document.security; + /// //Gets the PDF permission. + /// PdfPermissions permissions = security.permissions; + /// //Gets the permission option at index 0. + /// PdfPermissionsFlags permission = permissions[0]; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfPermissionsFlags operator [](int index) => _returnValue(index); + + /// Iterate for each element in permissions and perform action. + /// + /// ```dart + /// //Load encrypted PDF document with password. + /// PdfDocument document = PdfDocument(inputBytes: pdfData, password: 'password'); + /// //Document security + /// PdfSecurity security = document.security; + /// //Gets the PDF permission. + /// PdfPermissions permissions = security.permissions; + /// List permissions = [ + /// PdfPermissionsFlags.print, + /// PdfPermissionsFlags.editContent, + /// PdfPermissionsFlags.copyContent, + /// PdfPermissionsFlags.editAnnotations, + /// PdfPermissionsFlags.fillFields, + /// PdfPermissionsFlags.accessibilityCopyContent, + /// PdfPermissionsFlags.assembleDocument, + /// PdfPermissionsFlags.fullQualityPrint]; + /// //Check the PDF document encrypted with all permissions options. + /// bool hasAllPermissions = true; + /// permissions.forEach((PdfPermissionsFlags flag) { + /// if (flag != PdfPermissionsFlags.none) { + /// hasAllPermissions &= permissions.contains(flag); + /// } + /// }); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + void forEach(void Function(PdfPermissionsFlags element) action) { + final int length = count; + for (int i = 0; i < count; i++) { + action(this[i]); + if (length != count) { + throw ConcurrentModificationError(this); + } + } + } + + /// Get the permissions count. + /// + /// ```dart + /// //Load encrypted PDF document with password. + /// PdfDocument document = PdfDocument(inputBytes: pdfData, password: 'password'); + /// //Document security + /// PdfSecurity security = document.security; + /// //Gets the PDF permission. + /// PdfPermissions permissions = security.permissions; + /// //Gets the permissions count. + /// int count = permissions.count; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + int get count => _permissions.length; + + /// Add the permission. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Document security + /// PdfSecurity security = document.security; + /// //Add permission. + /// security.permissions.add(PdfPermissionsFlags.editContent); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + void add(PdfPermissionsFlags permission) { + ArgumentError.checkNotNull(permission, 'value cannot be null'); + if (!_permissions.contains(permission)) { + _permissions.add(permission); + _encryptor.permissions = _permissions; + _encryptor.encrypt = true; + _modifiedPermissions = true; + } + } + + /// Add the permissions. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Document security + /// PdfSecurity security = document.security; + /// //Add permissions. + /// security.permissions.addAll([ + /// PdfPermissionsFlags.editContent, + /// PdfPermissionsFlags.copyContent]); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + void addAll(List permission) { + ArgumentError.checkNotNull(permission, 'value cannot be null'); + bool isChanged = false; + if (permission.isNotEmpty) { + permission.forEach((PdfPermissionsFlags flag) { + if (!_permissions.contains(flag)) { + _permissions.add(flag); + isChanged = true; + _modifiedPermissions = true; + } + }); + } + if (isChanged) { + _encryptor.permissions = _permissions; + _encryptor.encrypt = true; + _modifiedPermissions = true; + } + } + + /// Remove permissions from an existing PDF. + /// + /// ```dart + /// //Load encrypted PDF document with password. + /// PdfDocument document = PdfDocument(inputBytes: pdfData, password: 'password'); + /// //Document security + /// PdfSecurity security = document.security; + /// //Gets the pdf permission. + /// PdfPermissions permissions = security.permissions; + /// //Remove permission. + /// permissions.remove(PdfPermissionsFlags.editContent); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + void remove(PdfPermissionsFlags permission) { + ArgumentError.checkNotNull(permission, 'value cannot be null'); + if (_permissions.contains(permission)) { + _permissions.remove(permission); + _encryptor.permissions = _permissions; + _encryptor.encrypt = true; + _modifiedPermissions = true; + } + } + + /// Remove all permissions from an existing PDF and set default. + /// + /// ```dart + /// //Load encrypted PDF document with password. + /// PdfDocument document = PdfDocument(inputBytes: pdfData, password: 'password'); + /// //Document security + /// PdfSecurity security = document.security; + /// //Gets the pdf permission. + /// PdfPermissions permissions = security.permissions; + /// //Remove all permissions and set default. + /// permissions.clear(); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + void clear() { + if (!(_permissions.contains(PdfPermissionsFlags.none) && + _permissions.length == 1)) { + _permissions = [PdfPermissionsFlags.none]; + _encryptor.permissions = _permissions; + _encryptor.encrypt = true; + _modifiedPermissions = true; + } + } + + PdfPermissionsFlags _returnValue(int index) { + ArgumentError.checkNotNull(index, 'Index cannot be null'); + if (index < 0 || index >= count) { + throw ArgumentError.value(index, 'Index out of range'); + } + return _permissions[index]; + } +} 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 4713653c2..7eb23fa57 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 @@ -37,7 +37,6 @@ class PdfGrid extends PdfLayoutElement { bool _hasColumnSpan; bool _hasRowSpan; PdfFont _defaultFont; - DataTable _dataSource; bool _headerRow = true; bool _bandedRow = true; bool _bandedColumn; @@ -73,16 +72,6 @@ class PdfGrid extends PdfLayoutElement { return _headers; } - /// Gets the data bind to the [PdfGrid] by associating it with an external data source. - DataTable get dataSource => _dataSource; - - /// Sets the data bind to the [PdfGrid] by associating it with an external data source. - set dataSource(DataTable value) { - ArgumentError.checkNotNull(value); - _dataSource = value; - _setData(this, value); - } - /// Gets the grid style. PdfGridStyle get style { _style ??= PdfGridStyle(); @@ -131,46 +120,6 @@ class PdfGrid extends PdfLayoutElement { _defaultBorder = PdfBorders(); } - PdfGrid _setData(PdfGrid grid, DataTable source) { - final List dataColumns = source.columns; - if (dataColumns.isNotEmpty) { - grid.headers.clear(); - grid.rows._rows.clear(); - grid.rows._rows.length = 0; - grid.columns.add(count: dataColumns.length); - grid.headers.add(1); - for (int i = 0; i < dataColumns.length; i++) { - final Widget cell = dataColumns[i].label; - if (cell is Text) { - grid.headers[0].cells[i].value = cell.data; - } else if (cell is DataTable) { - grid.headers[0].cells[i].value = _setData(PdfGrid(), cell); - } - } - } - final List dataRows = source.rows; - if (dataRows.isNotEmpty) { - final int columnCount = dataRows[0].cells.length; - if (grid.columns.count <= 0 && columnCount > 0) { - grid.columns.add(count: columnCount); - } - for (int i = 0; i < dataRows.length; i++) { - final DataRow dataRow = dataRows[i]; - final PdfGridRow gridRow = grid.rows.add(); - for (int j = 0; j < columnCount; j++) { - final DataCell dataCell = dataRow.cells[j]; - final Widget cell = dataCell.child; - if (cell is Text) { - gridRow.cells[j].value = cell.data; - } else if (cell is DataTable) { - gridRow.cells[j].value = _setData(PdfGrid(), cell); - } - } - } - } - return grid; - } - _Size _measure() { double height = 0; final double width = columns._width; 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 new file mode 100644 index 000000000..1b9bd8686 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/xmp/xmp_metadata.dart @@ -0,0 +1,337 @@ +part of pdf; + +/// Represents XMP metadata of the document. +class _XmpMetadata extends _IPdfWrapper { + //Constructor + /// Initializes a new instance of the [XmpMetadata] class. + _XmpMetadata(PdfDocumentInformation documentInfo) { + _initialize(documentInfo); + } + + /// Initializes a new instance of the [XmpMetadata] class. + _XmpMetadata.fromXmlDocument(XmlDocument xmp) { + ArgumentError.checkNotNull(xmp, 'xmpMetadata'); + _stream = _PdfStream(); + _stream._beginSave = _beginSave; + _stream._endSave = _endSave; + load(xmp); + } + + //Fields + XmlDocument _xmlData; + _PdfStream _stream; + PdfDocumentInformation _documentInfo; + Map _namespaceCollection = {}; + final String _rdfUri = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + final String _xap = 'http://ns.adobe.com/xap/1.0/'; + final String _dublinSchema = 'http://purl.org/dc/elements/1.1/'; + + //Properties + /// Gets XMP data in XML format. + XmlDocument get xmlData => _xmlData; + + //Gets RDF element of the packet. + XmlElement get _rdf { + XmlElement node; + for (final element in _xmlData.descendants) { + if (element is XmlElement && element.name.local == 'RDF') { + node = element; + break; + } + } + final String elmName = _xmlData.rootElement.name.toString(); + if (node == null) { + for (final element in _xmlData.descendants) { + if (element is XmlElement && element.name.local == elmName) { + node = element; + break; + } + } + ArgumentError.checkNotNull(node, 'node'); + } + return node; + } + + //Gets xmpmeta element of the packet. + XmlElement get _xmpmeta { + XmlElement node; + int count = 0; + for (final element in _xmlData.descendants) { + if (element is XmlElement && element.name.local == 'xmpmeta') { + node = element; + count++; + if (count > 1) { + throw ArgumentError( + 'More than one element satisfies the specified condition'); + } + } + } + ArgumentError.checkNotNull(node, 'node'); + return node; + } + + //Public methods. + /// Loads XMP from the XML. + void load(XmlDocument xmp) { + ArgumentError.checkNotNull(xmp, 'xmp'); + _reset(); + _xmlData = xmp; + _importNamespaces(_xmlData); + } + + /// Adds schema to the XMP in XML format. + void add(XmlElement schema) { + ArgumentError.checkNotNull(schema, 'schema'); + // Import namespaces. + _addNamespace(schema.name.prefix, schema.name.namespaceUri ?? ''); + // Append schema. + _rdf.children.add(schema); + } + + //Implementations + void _initialize(PdfDocumentInformation info) { + _xmlData = XmlDocument(); + _stream = _PdfStream(); + _documentInfo = info; + _initializeStream(); + _createStartPacket(); + _createXmpmeta(); + _createRdf(_documentInfo); + _createEndPacket(); + } + + //Initialize stream. + void _initializeStream() { + _stream._beginSave = _beginSave; + _stream._endSave = _endSave; + _stream[_DictionaryProperties.type] = + _PdfName(_DictionaryProperties.metadata); + _stream[_DictionaryProperties.subtype] = + _PdfName(_DictionaryProperties.xml); + _stream.compress = false; + } + + //Raises before stream saves. + void _beginSave(Object sender, _SavePdfPrimitiveArgs ars) { + //Save Xml to the stream. + final _PdfStreamWriter streamWriter = _PdfStreamWriter(_stream); + streamWriter._write(_xmlData.toXmlString(pretty: true)); + } + + //Raises after stream saves. + void _endSave(Object sender, _SavePdfPrimitiveArgs ars) { + //Reset stream data + _stream._clearStream(); + } + + //Creates packet element. + void _createStartPacket() { + const String startPacket = + 'begin=\"\uFEFF" id=\"W5M0MpCehiHzreSzNTczkc9d\"'; + _xmlData.children.add(XmlProcessing('xpacket', startPacket)); + } + + //Creates packet element. + void _createEndPacket() { + const String endPacket = 'end="r"'; + _xmlData.children.add(XmlProcessing('xpacket', endPacket)); + } + + //Creates xmpmeta element. + void _createXmpmeta() { + final XmlElement element = _createElement('x', 'xmpmeta', 'adobe:ns:meta/'); + _xmlData.children.add(element); + } + + //Creates Resource Description Framework element. + void _createRdf(PdfDocumentInformation info) { + 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); + rdfDescription.setAttribute('rdf:about', ' '); + if (!_isNullOrEmpty(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)])); + } + rdf.children.add(rdfDescription); + } + if (!_isNullOrEmpty(info.creator) || + info._creationDate != null || + info._modificationDate != null) { + 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)])); + } + if (info._creationDate != null) { + final String createDate = _getDateTime(info._creationDate); + xmpDescription.children.add(XmlElement( + XmlName('CreateDate', 'xmp'), [], [XmlText(createDate)])); + } + if (info._modificationDate != null && !info._isRemoveModifyDate) { + final String modificationDate = _getDateTime(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); + dublinDescription.setAttribute('rdf:about', ' '); + dublinDescription.setAttribute('xmlns:dc', dublinNamespace); + dublinDescription.children.add( + XmlElement(XmlName('format', 'dc'), [], [XmlText('application/pdf')])); + _createDublinCoreContainer( + dublinDescription, 'title', info.title, true, 'Alt'); + _createDublinCoreContainer( + dublinDescription, 'description', info.subject, true, 'Alt'); + _createDublinCoreContainer( + dublinDescription, 'subject', info.keywords, false, 'Bag'); + _createDublinCoreContainer( + dublinDescription, 'creator', info.author, false, 'Seq'); + rdf.children.add(dublinDescription); + if (_documentInfo._conformance == PdfConformanceLevel.a1b || + _documentInfo._conformance == PdfConformanceLevel.a2b || + _documentInfo._conformance == PdfConformanceLevel.a3b) { + final String pdfaid = + _addNamespace('pdfaid', 'http://www.aiim.org/pdfa/ns/id/'); + final XmlElement pdfA = _createElement('rdf', 'Description', _rdfUri); + pdfA.setAttribute('rdf:about', ' '); + if (_documentInfo._conformance == PdfConformanceLevel.a1b) { + pdfA.setAttribute('pdfaid:part', '1'); + } else if (_documentInfo._conformance == PdfConformanceLevel.a2b) { + pdfA.setAttribute('pdfaid:part', '2'); + } else { + pdfA.setAttribute('pdfaid:part', '3'); + } + pdfA.setAttribute('pdfaid:conformance', 'B'); + pdfA.setAttribute('xmlns:pdfaid', pdfaid); + rdf.children.add(pdfA); + } else { + _addNamespace('pdfaid', _rdfUri); + } + _xmpmeta.children.add(rdf); + } + + bool _isNullOrEmpty(String text) { + return text == null || text == ''; + } + + XmlElement _createElement( + String prefix, String localName, String namespaceURI) { + ArgumentError.checkNotNull(prefix, 'prefix'); + ArgumentError.checkNotNull(localName, 'localName'); + XmlElement element; + if (!_namespaceCollection.containsKey(prefix) && + prefix != 'xml' && + prefix != 'xmlns') { + element = XmlElement(XmlName(localName, prefix), + [XmlAttribute(XmlName(prefix, 'xmlns'), namespaceURI)], [], false); + } else { + element = + XmlElement(XmlName(localName, prefix == 'xap' ? 'xmp' : prefix)); + } + _addNamespace(prefix, namespaceURI); + return element; + } + + String _addNamespace(String prefix, String namespaceURI) { + ArgumentError.checkNotNull(prefix, 'prefix'); + ArgumentError.checkNotNull(namespaceURI, 'namespaceURI'); + String result = namespaceURI; + if (!_namespaceCollection.containsKey(prefix) && + prefix != 'xml' && + prefix != 'xmlns') { + _namespaceCollection[prefix] = namespaceURI; + } else { + result = _namespaceCollection[prefix]; + } + return result; + } + + String _getDateTime(DateTime dateTime) { + final int regionMinutes = dateTime.timeZoneOffset.inMinutes ~/ 11; + String offsetMinutes = regionMinutes.toString(); + if (regionMinutes >= 0 && regionMinutes <= 9) { + offsetMinutes = '0' + offsetMinutes; + } + final int regionHours = dateTime.timeZoneOffset.inHours; + String offsetHours = regionHours.toString(); + if (regionHours >= 0 && regionHours <= 9) { + offsetHours = '0' + offsetHours; + } + final String date = dateTime.toIso8601String().substring(0, 22) + + '+' + + offsetHours + + ':' + + offsetMinutes; + return date; + } + + //Creates a Dublin core containers. + void _createDublinCoreContainer(XmlElement dublinDesc, String containerName, + String value, bool defaultLang, String element) { + if (!_isNullOrEmpty(value)) { + final XmlElement title = + _createElement('dc', containerName, _dublinSchema); + _addNamespace('rdf', _rdfUri); + final XmlElement alt = _createElement('rdf', element, _rdfUri); + XmlElement li = _createElement('rdf', 'li', _rdfUri); + if (containerName == 'Subject') { + final List values = value.split(','); + for (int i = 0; i < values.length; i++) { + if (i > 0) { + li = _createElement('rdf', 'li', _rdfUri); + } + li.innerText = values[i]; + alt.children.add(li); + } + } else { + li.innerText = value; + alt.children.add(li); + } + title.children.add(alt); + dublinDesc.children.add(title); + if (defaultLang) { + li.setAttribute('xml:lang', 'x-default'); + } + } + } + + //Resets current xmp metadata. + void _reset() { + _xmlData = null; + _namespaceCollection = {}; + } + + void _importNamespaces(XmlDocument xml) { + for (final element in xml.descendants) { + if (element is XmlElement) { + if (!_namespaceCollection.containsKey(element.name.prefix)) { + _namespaceCollection[element.name.prefix] = element.name.namespaceUri; + } + } + } + } + + @override + _IPdfPrimitive get _element => _stream; +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_primitive.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_primitive.dart index 4aeeee4f2..b2e6aefb2 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_primitive.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_primitive.dart @@ -22,4 +22,6 @@ class _IPdfPrimitive { void save(_IPdfWriter writer) {} void dispose() {} + + _IPdfPrimitive _clone(_PdfCrossTable crossTable) => null; } diff --git a/packages/syncfusion_flutter_pdf/pubspec.yaml b/packages/syncfusion_flutter_pdf/pubspec.yaml index 709e039d2..53ae59d51 100644 --- a/packages/syncfusion_flutter_pdf/pubspec.yaml +++ b/packages/syncfusion_flutter_pdf/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_pdf description: Syncfusion Flutter PDF is a library written natively in Dart for creating PDF documents from scratch. -version: 18.3.35-beta +version: 18.1.36-beta homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdf environment: @@ -10,5 +10,6 @@ dependencies: flutter: sdk: flutter intl: ">=0.15.0 <0.17.0" + xml: ^4.5.1 syncfusion_flutter_core: path: ../syncfusion_flutter_core diff --git a/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md b/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md index 3624c0fd3..1ce274b64 100644 --- a/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md +++ b/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md @@ -1,3 +1,26 @@ +## [unreleased] + +**Features** + +* Text Search - Select text presented in a PDF document. +* Text Selection - Search for text and navigate to all its occurrences in a PDF document instantly. +* Document Link Annotation - Navigate to the desired topic or position by tapping the document link annotation of the topics in the table of contents in a PDF document. +* Support to adjust the space between the pages has been provided. +* Provided `initialScrollOffset` and `initialZoomLevel` property to display the PDF document loaded with the specified scroll offset and zoom level respectively. + +## [18.3.53-beta] - 12/08/2020 + +**Features** + +* Page storage support has been provided, which preserves scroll offset and zoom level. +* Now, the async operation will be cancelled in case widget is being disposed and added mounted checks. + +## [18.3.47-beta] - 11/05/2020 + +**Features** + +* Now, the temporary PDF file created by Syncfusion Flutter SfPdfViewer will be inaccessible. + ## [18.3.35-beta] - 10/01/2020 Initial release. diff --git a/packages/syncfusion_flutter_pdfviewer/README.md b/packages/syncfusion_flutter_pdfviewer/README.md index a394016b8..804991c3f 100644 --- a/packages/syncfusion_flutter_pdfviewer/README.md +++ b/packages/syncfusion_flutter_pdfviewer/README.md @@ -57,7 +57,7 @@ Explore the full capabilities of our Flutter widgets on your device by installin 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-pdfviewer) +* [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 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 6228d9d96..35c6889d6 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 @@ -16,6 +16,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -24,7 +25,6 @@ import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry.Registrar; /** SyncfusionFlutterPdfViewerPlugin */ public class SyncfusionFlutterPdfViewerPlugin implements FlutterPlugin, MethodCallHandler { @@ -46,6 +46,8 @@ public class SyncfusionFlutterPdfViewerPlugin implements FlutterPlugin, MethodCa ParcelFileDescriptor fileDescriptor; /// PDF document path. String pdfPath; + /// PDF Runnable + PdfRunnable bitmapRunnable; @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "syncfusion_flutter_pdfviewer"); @@ -61,9 +63,10 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBindin // 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. - public static void registerWith(Registrar registrar) { + @SuppressWarnings("deprecation") + public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), "syncfusion_flutter_pdfviewer"); - channel.setMethodCallHandler(new SyncfusionFlutterPdfViewerPlugin()); + channel.setMethodCallHandler(new SyncfusionFlutterPdfViewerPlugin()); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @@ -167,7 +170,7 @@ void getImage(int pageIndex, String path) { try { reinitializePdfRenderer(path); ExecutorService executor = Executors.newCachedThreadPool(); - PdfRunnable bitmapRunnable = new PdfRunnable(renderer, resultPdf, pageIndex); + bitmapRunnable = new PdfRunnable(renderer, resultPdf, pageIndex); executor.submit(bitmapRunnable); } catch (Exception e) { resultPdf.error(e.getMessage(), e.getLocalizedMessage(), e.getMessage()); @@ -181,6 +184,7 @@ boolean dispose() { if (pageHeight != null) pageHeight = null; if (renderer != null) { + bitmapRunnable.dispose(); renderer.close(); renderer = null; } @@ -199,20 +203,32 @@ boolean dispose() { /// This runnable executes all the image fetch in separate thread. class PdfRunnable implements Runnable { - private ArrayList bitmaps = null; + private List bitmaps = null; private PdfRenderer renderer; private Result resultPdf; private int pageIndex; + private PdfRenderer.Page page; - PdfRunnable(PdfRenderer renderer, Result resultPdf,int pageIndex) { + PdfRunnable(PdfRenderer renderer, Result resultPdf, int pageIndex) { this.resultPdf = resultPdf; this.renderer = renderer; this.pageIndex = pageIndex; } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public void run() { - final PdfRenderer.Page page = renderer.openPage(pageIndex - 1); + public void dispose() + { + bitmaps = null; + if(page != null) + { + page.close(); + page = null; + } + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void run() { + page = renderer.openPage(pageIndex - 1); int width = (int) (page.getWidth() * 1.75); int height = (int) (page.getHeight() * 1.75); final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); @@ -220,6 +236,7 @@ public void run() { final Rect rect = new Rect(0, 0, width, height); page.render(bitmap, rect, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); page.close(); + page = null; ByteArrayOutputStream outStream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream); bitmaps = new ArrayList<>(); diff --git a/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift b/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift index 56367bd25..8f4c0f787 100644 --- a/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift +++ b/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift @@ -70,16 +70,10 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { // Initializes the PDF Renderer and returns the page count. private func initializePdfRenderer( call: FlutterMethodCall, result: @escaping FlutterResult) { - do{ - self.url = try URL(fileURLWithPath: call.arguments as! String) - self.document = try CGPDFDocument(url as CFURL)! + self.url = URL(fileURLWithPath: call.arguments as! String) + self.document = CGPDFDocument(url as CFURL) self.pageCount = NSNumber(value: self.document!.numberOfPages) result(self.pageCount.stringValue); - } - catch - { - result("Flutter Plugin Error"); - } } // Reinitailize PDF Renderer if the given document URL is different from current document URL @@ -88,7 +82,7 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { if(self.url == nil || self.url.absoluteString != path.absoluteString) { self.url = path - self.document = try CGPDFDocument(url as CFURL)! + self.document = CGPDFDocument(url as CFURL)! self.pageCount = NSNumber(value: self.document!.numberOfPages) } } 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 e74910bf5..1e3a081c5 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart @@ -95,6 +95,7 @@ class BookmarkViewControllerState extends State { } Future _handleClose() async { + widget.controller.clearSelection(); if (showBookmark) { setState(() { _isExpanded = 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 ff387084c..17e7d1faf 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_provider.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_provider.dart @@ -51,7 +51,8 @@ class NetworkPdf extends PdfProvider { final HttpClientRequest request = await client.getUrl(Uri.parse(_url)); final HttpClientResponse response = await request.close(); final bytes = await consolidateHttpClientResponseBytes(response); - final Directory directory = await path_provider.getTemporaryDirectory(); + Directory directory = await path_provider.getTemporaryDirectory(); + directory = await directory.createTemp('.syncfusion'); final filename = _url.substring(_url.lastIndexOf('/') + 1); File sample = File('${directory.path}/$filename'); sample = await sample.writeAsBytes(bytes, flush: true); @@ -88,7 +89,8 @@ class MemoryPdf extends PdfProvider { @override Future getPdfPath(BuildContext context) async { try { - final Directory directory = await path_provider.getTemporaryDirectory(); + Directory directory = await path_provider.getTemporaryDirectory(); + directory = await directory.createTemp('.syncfusion'); final File sample = File('${directory.path}/sample.pdf'); await sample.writeAsBytes(_bytes, flush: true); _pdfBytes = _bytes; @@ -135,7 +137,8 @@ class AssetPdf extends PdfProvider { @override Future getPdfPath(BuildContext context) async { try { - final Directory directory = await path_provider.getTemporaryDirectory(); + Directory directory = await path_provider.getTemporaryDirectory(); + directory = await directory.createTemp('.syncfusion'); final filename = _pdfPath.substring(_pdfPath.lastIndexOf('/') + 1); final File sample = File('${directory.path}/$filename'); final bytes = await ((_bundle != null) @@ -172,7 +175,8 @@ class FilePdf extends PdfProvider { @override Future getPdfPath(BuildContext context) async { try { - final Directory directory = await path_provider.getTemporaryDirectory(); + Directory directory = await path_provider.getTemporaryDirectory(); + directory = await directory.createTemp('.syncfusion'); final filename = _file.path.substring(_file.path.lastIndexOf('/') + 1); final File sample = File('${directory.path}/$filename'); final Uint8List bytes = File(_file.path).readAsBytesSync(); diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart new file mode 100644 index 000000000..1dfc83625 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart @@ -0,0 +1,48 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; + +/// TextSelectionHelper for storing information of text selection. +class TextSelectionHelper { + /// It will be true,if text selection is started. + bool selectionEnabled = false; + + /// firstGlyphOffset of the selection. + Offset firstGlyphOffset; + + /// Page number in which text selection is started. + int viewId; + + /// Represents the global region of text selection. + Rect globalSelectedRegion; + + /// Copied text of text selection. + String copiedText; + + /// X position of start bubble. + double startBubbleX; + + /// Y position of start bubble. + double startBubbleY; + + /// X position of end bubble. + double endBubbleX; + + /// Y position of end bubble. + double endBubbleY; + + /// heightPercentage of pdf page + double heightPercentage; + + /// TextLine from Pdf library. + List textLines; + + /// Line of the start bubble. + TextLine startBubbleLine; + + /// Line of the end bubble. + TextLine endBubbleLine; + + /// Entry history of text selection + LocalHistoryEntry historyEntry; +} 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 4014ff53f..38bcba7df 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart @@ -42,7 +42,9 @@ class PdfViewerPlugin { Future> _getImage(int pageIndex) async { final List images = await _channel.invokeMethod( 'getimage', {'index': pageIndex, 'path': _pdfPath}); - _renderedPages[pageIndex] = images[0]; + if (_renderedPages != null) { + _renderedPages[pageIndex] = images[0]; + } return _renderedPages; } @@ -53,18 +55,18 @@ class PdfViewerPlugin { for (int pageIndex = startPageIndex; pageIndex <= endPageIndex; pageIndex++) { - if (!_renderedPages.containsKey(pageIndex)) { + if (_renderedPages != null && !_renderedPages.containsKey(pageIndex)) { await _getImage(pageIndex); } } final pdfPage = []; - _renderedPages.forEach((key, value) { + _renderedPages?.forEach((key, value) { if (!(key >= startPageIndex && key <= endPageIndex)) { pdfPage.add(key); } }); pdfPage.forEach((index) { - _renderedPages.remove(index); + _renderedPages?.remove(index); }); return _renderedPages; } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_container.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_container.dart index ad82cfd05..c77704e78 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_container.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_container.dart @@ -13,6 +13,8 @@ class PdfContainer extends StatefulWidget { /// Creates a instance of PdfContainer. PdfContainer({ Key key, + this.initialScrollOffset, + this.initialZoomLevel, this.enableDoubleTapZooming = true, this.onZoomLevelChanged, this.pdfController, @@ -27,6 +29,12 @@ class PdfContainer extends StatefulWidget { assert(enableDoubleTapZooming != null), super(key: key); + /// Represents the initial zoom level to be applied when the [SfPdfViewer] widget is loaded. + final double initialZoomLevel; + + /// Represents the initial scroll offset position to be displayed when the [SfPdfViewer] widget is loaded. + final Offset initialScrollOffset; + /// An object that can be used to control the position to which this scroll /// view is scrolled. final ScrollController scrollController; @@ -700,6 +708,17 @@ class PdfContainerState extends State _pdfController = widget.pdfController; _pdfController?.addListener(_onControllerValueChange); viewMatrix = Matrix4.identity(); + if (widget.initialZoomLevel > 1) { + if (widget.initialZoomLevel >= _minScale && + widget.initialZoomLevel <= _maxScale) { + viewMatrix = _matrixScale( + viewMatrix, + widget.initialZoomLevel, + ); + _scale = widget.initialZoomLevel; + _previousScale = _scale; + } + } if (_pdfController.zoomLevel != null) { if (_pdfController.zoomLevel < _minScale || _pdfController.zoomLevel > _maxScale) { @@ -707,16 +726,25 @@ class PdfContainerState extends State } else { viewMatrix = _matrixScale( viewMatrix, - _pdfController.zoomLevel, + (widget.initialZoomLevel > _pdfController.zoomLevel) + ? _pdfController.zoomLevel / widget.initialZoomLevel + : _pdfController.zoomLevel, ); + _scale = _pdfController.zoomLevel; - _previousScale = _scale; + _previousScale = widget.initialZoomLevel; } } + _animationController = AnimationController( vsync: this, ); - + if (widget.initialScrollOffset != null) { + Future.delayed(Duration.zero, () async { + viewMatrix = _matrixTranslate( + viewMatrix, Offset(widget.initialScrollOffset.dx, 0)); + }); + } // Added a listener for the scrolling. widget.scrollController.addListener(_updateOrigin); } 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 8602d466c..450813953 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,7 +1,9 @@ import 'dart:core'; import 'dart:typed_data'; import 'dart:ui'; - +import 'package:syncfusion_flutter_pdf/pdf.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/control/pdfviewer_canvas.dart'; +import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -9,7 +11,25 @@ import 'package:flutter/material.dart'; /// Wrapper class of [Image] widget which shows the PDF pages as an image class PdfPageView extends StatefulWidget { /// Constructs PdfPageView instance with the given parameters. - PdfPageView({Key key, this.imageStream, this.width, this.height}) + PdfPageView( + {Key key, + this.imageStream, + this.width, + this.height, + this.pageSpacing, + this.pdfDocument, + this.pdfPages, + this.pageIndex, + this.scrollController, + this.pdfViewerController, + this.enableDocumentLinkAnnotation, + this.enableTextSelection, + this.onTextSelectionChanged, + this.onTextSelectionDragStarted, + this.onTextSelectionDragEnded, + this.searchTextHighlightColor, + this.textCollection, + this.pdfTextSearchResult}) : super(key: key); /// Image stream @@ -20,15 +40,63 @@ class PdfPageView extends StatefulWidget { /// Height of page final double height; + + /// Space between pages + final double pageSpacing; + + /// Instance of [PdfDocument] + final PdfDocument pdfDocument; + + /// If true, document link annotation is enabled. + final bool enableDocumentLinkAnnotation; + + /// Index of page + final int pageIndex; + + /// Information about PdfPage + final Map pdfPages; + + /// Instance of [ScrollController] + final ScrollController scrollController; + + /// Instance of [PdfViewerController] + final PdfViewerController pdfViewerController; + + /// If false,text selection is disabled.Default value is true. + final bool enableTextSelection; + + /// Triggers when text selection is changed. + final PdfTextSelectionChangedCallback onTextSelectionChanged; + + /// Triggers when text selection dragging started. + final VoidCallback onTextSelectionDragStarted; + + /// Triggers when text selection dragging ended. + final VoidCallback onTextSelectionDragEnded; + + ///Highlighting color of searched text + final Color searchTextHighlightColor; + + ///searched text details + final List textCollection; + + /// PdfTextSearchResult instance + final PdfTextSearchResult pdfTextSearchResult; + @override State createState() { - return _PdfPageViewState(); + return PdfPageViewState(); } } /// State for [PdfPageView] -class _PdfPageViewState extends State { +class PdfPageViewState extends State { SfPdfViewerThemeData _pdfViewerThemeData; + final GlobalKey _canvasKey = GlobalKey(); + + /// CanvasRenderBox getter for accessing canvas properties. + CanvasRenderBox get canvasRenderBox => + _canvasKey?.currentContext?.findRenderObject(); @override void didChangeDependencies() { @@ -45,30 +113,86 @@ class _PdfPageViewState extends State { @override Widget build(BuildContext context) { if (widget.imageStream != null) { - final Widget page = Image.memory(widget.imageStream, + final Widget page = Container( + height: widget.height + widget.pageSpacing, + child: Column(children: [ + Image.memory(widget.imageStream, + width: widget.width, + height: widget.height, + fit: BoxFit.fitWidth, + alignment: Alignment.center), + Container( + height: widget.pageSpacing, + color: _pdfViewerThemeData.backgroundColor, + ) + ])); + if (widget.textCollection != null) { + final Widget canvas = PdfViewerCanvas( + key: _canvasKey, + pdfDocument: widget.pdfDocument, width: widget.width, height: widget.height, - fit: BoxFit.fitWidth, - alignment: Alignment.center); - return Container( - color: Colors.white, - foregroundDecoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: 4, color: _pdfViewerThemeData.backgroundColor), + pageIndex: widget.pageIndex, + pdfPages: widget.pdfPages, + scrollController: widget.scrollController, + pdfViewerController: widget.pdfViewerController, + enableDocumentLinkNavigation: widget.enableDocumentLinkAnnotation, + onTextSelectionChanged: widget.onTextSelectionChanged, + onTextSelectionDragStarted: widget.onTextSelectionDragStarted, + onTextSelectionDragEnded: widget.onTextSelectionDragEnded, + enableTextSelection: widget.enableTextSelection, + searchTextHighlightColor: widget.searchTextHighlightColor, + textCollection: widget.textCollection, + pdfTextSearchResult: widget.pdfTextSearchResult, + ); + + return Stack( + children: [ + Container( + color: Colors.white, + child: page, + ), + Container( + height: widget.height, width: widget.width, child: canvas), + ], + ); + } else { + return Stack(children: [ + Container( + color: Colors.white, + child: page, ), - ), - child: page, - ); + Container( + width: widget.width, + height: widget.height, + child: PdfViewerCanvas( + key: _canvasKey, + pdfDocument: widget.pdfDocument, + width: widget.width, + height: widget.height, + pageIndex: widget.pageIndex, + pdfPages: widget.pdfPages, + scrollController: widget.scrollController, + pdfViewerController: widget.pdfViewerController, + enableDocumentLinkNavigation: + widget.enableDocumentLinkAnnotation, + onTextSelectionChanged: widget.onTextSelectionChanged, + onTextSelectionDragStarted: widget.onTextSelectionDragStarted, + onTextSelectionDragEnded: widget.onTextSelectionDragEnded, + enableTextSelection: widget.enableTextSelection), + ), + ]); + } } else { return Container( - height: widget.height, + height: widget.height + widget.pageSpacing, width: widget.width, color: Colors.white, foregroundDecoration: BoxDecoration( border: Border( bottom: BorderSide( - width: 4, color: _pdfViewerThemeData.backgroundColor), + width: widget.pageSpacing, + color: _pdfViewerThemeData.backgroundColor), ), ), ); 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 ec54256ef..0381b58b6 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,3 +1,5 @@ +import 'dart:ui'; + import 'package:syncfusion_flutter_pdf/pdf.dart'; /// Holds the details for the [SfPdfViewer.onPageChanged] callback, @@ -100,3 +102,28 @@ class PdfDocumentLoadFailedDetails { return _description; } } + +/// Holds the details for the [SfPdfViewer.onTextSelectionChanged] callback, +/// such as [globalSelectedRegion] and [selectedText]. +class PdfTextSelectionChangedDetails { + /// + PdfTextSelectionChangedDetails( + String selectedText, Rect globalSelectedRegion) { + _selectedText = selectedText; + _globalSelectedRegion = globalSelectedRegion; + } + + String _selectedText; + + /// Selected text value. + String get selectedText { + return _selectedText; + } + + Rect _globalSelectedRegion; + + /// The global bounds information of the selected text region. + Rect get globalSelectedRegion { + return _globalSelectedRegion; + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart new file mode 100644 index 000000000..5d563ffdc --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart @@ -0,0 +1,1001 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.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'; + +/// Instance of TextSelectionHelper. +TextSelectionHelper _textSelectionHelper = TextSelectionHelper(); + +/// [PdfViewerCanvas] is a layer above the PDF page over which annotations, text selection, and text search UI level changes will be applied. +class PdfViewerCanvas extends LeafRenderObjectWidget { + /// Constructs PdfViewerCanvas instance with the given parameters. + PdfViewerCanvas({ + Key key, + this.height, + this.width, + this.pdfDocument, + this.pageIndex, + this.pdfPages, + this.scrollController, + this.pdfViewerController, + this.enableDocumentLinkNavigation, + this.enableTextSelection, + this.onTextSelectionChanged, + this.onTextSelectionDragStarted, + this.onTextSelectionDragEnded, + this.textCollection, + this.searchTextHighlightColor, + this.pdfTextSearchResult, + }) : super(key: key); + + /// Height of page + final double height; + + /// If true, document link annotation is enabled. + final bool enableDocumentLinkNavigation; + + /// Width of Page + final double width; + + /// Instance of [PdfDocument] + final PdfDocument pdfDocument; + + /// Index of page + final int pageIndex; + + /// Information about PdfPage + final Map pdfPages; + + /// Instance of [ScrollController] + final ScrollController scrollController; + + /// PdfViewer controller. + final PdfViewerController pdfViewerController; + + /// If false,text selection is disabled.Default value is true. + final bool enableTextSelection; + + /// Triggers when text selection dragging started. + final VoidCallback onTextSelectionDragStarted; + + /// Triggers when text selection dragging ended. + final VoidCallback onTextSelectionDragEnded; + + /// Triggers when text selection is changed. + final PdfTextSelectionChangedCallback onTextSelectionChanged; + + ///Highlighting color of searched text + final Color searchTextHighlightColor; + + ///searched text details + final List textCollection; + + /// PdfTextSearchResult instance + final PdfTextSearchResult pdfTextSearchResult; + + @override + RenderObject createRenderObject(BuildContext context) { + return CanvasRenderBox( + height: height, + width: width, + context: context, + pdfDocument: pdfDocument, + pageIndex: pageIndex, + pdfPages: pdfPages, + scrollController: scrollController, + pdfViewerController: pdfViewerController, + enableDocumentLinkNavigation: enableDocumentLinkNavigation, + enableTextSelection: enableTextSelection, + onTextSelectionChanged: onTextSelectionChanged, + onTextSelectionDragStarted: onTextSelectionDragStarted, + onTextSelectionDragEnded: onTextSelectionDragEnded, + textCollection: textCollection, + searchTextHighlightColor: searchTextHighlightColor, + pdfTextSearchResult: pdfTextSearchResult, + ); + } + + @override + void updateRenderObject(BuildContext context, CanvasRenderBox renderObject) { + renderObject + ..height = height + ..width = width + ..pageIndex = pageIndex + ..textCollection = textCollection + ..searchTextHighlightColor = searchTextHighlightColor + ..pdfTextSearchResult = pdfTextSearchResult; + + renderObject.markNeedsPaint(); + + super.updateRenderObject(context, renderObject); + } +} + +/// CanvasRenderBox for pdfViewer. +class CanvasRenderBox extends RenderBox { + /// Constructor of CanvasRenderBox. + CanvasRenderBox( + {this.height, + this.width, + this.context, + this.pdfDocument, + this.pdfPages, + this.pageIndex, + this.scrollController, + this.pdfViewerController, + this.enableDocumentLinkNavigation, + this.enableTextSelection, + this.onTextSelectionChanged, + this.onTextSelectionDragStarted, + this.onTextSelectionDragEnded, + this.textCollection, + this.searchTextHighlightColor, + this.pdfTextSearchResult}) { + final GestureArenaTeam team = GestureArenaTeam(); + _tapRecognizer = TapGestureRecognizer() + ..onTapUp = _handleTapUp + ..onTapDown = _handleTapDown; + _longPressRecognizer = LongPressGestureRecognizer() + ..onLongPressStart = _handleLongPressStart; + _dragRecognizer = PanGestureRecognizer() + ..team = team + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..onDown = _handleDragDown; + _verticalDragRecognizer = VerticalDragGestureRecognizer() + ..team = team + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd; + } + + /// Height of Page + double height; + + /// Width of page + double width; + + /// Index of page + int pageIndex; + + /// Instance of [PdfDocument] + final PdfDocument pdfDocument; + + /// BuildContext for canvas. + final BuildContext context; + + /// If false,text selection is disabled.Default value is true. + final bool enableTextSelection; + + /// If true, document link annotation is enabled. + final bool enableDocumentLinkNavigation; + + /// Information about PdfPage + final Map pdfPages; + + /// Instance of [ScrollController] + final ScrollController scrollController; + + /// PdfViewer controller. + final PdfViewerController pdfViewerController; + + /// Triggers when text selection dragging started. + final VoidCallback onTextSelectionDragStarted; + + /// Triggers when text selection dragging ended. + final VoidCallback onTextSelectionDragEnded; + + /// Triggers when text selection is changed. + final PdfTextSelectionChangedCallback onTextSelectionChanged; + + ///Highlighting color of searched text + Color searchTextHighlightColor; + + ///searched text details + List textCollection; + + /// PdfTextSearchResult instance + PdfTextSearchResult pdfTextSearchResult; + + int _viewId; + double _totalPageOffset; + bool _isTOCTapped = false; + double _startBubbleTapX = 0; + double _endBubbleTapX = 0; + final double _bubbleSize = 16.0; + final double _maximumZoomLevel = 2.0; + bool _longPressed = false; + bool _startBubbleDragging = false; + bool _endBubbleDragging = false; + double _zoomPercentage; + Offset _tapDetails; + Offset _dragDetails; + Offset _dragDownDetails; + TapGestureRecognizer _tapRecognizer; + PanGestureRecognizer _dragRecognizer; + LongPressGestureRecognizer _longPressRecognizer; + VerticalDragGestureRecognizer _verticalDragRecognizer; + PdfDocumentLinkAnnotation _documentLinkAnnotation; + + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + if (event is PointerDownEvent) { + _tapRecognizer.addPointer(event); + _longPressRecognizer.addPointer(event); + _dragRecognizer.addPointer(event); + if (_textSelectionHelper.selectionEnabled) { + final bool isStartDragPossible = _checkStartBubblePosition(); + final bool isEndDragPossible = _checkEndBubblePosition(); + if (isStartDragPossible || isEndDragPossible) { + _verticalDragRecognizer.addPointer(event); + } + } + } + super.handleEvent(event, entry); + } + + @override + bool hitTestSelf(Offset position) => true; + + @override + bool get sizedByParent => true; + + /// Handles the tap up event + void _handleTapUp(TapUpDetails details) { + if (textCollection == null) { + clearSelection(); + } + if (enableDocumentLinkNavigation) { + _viewId = pageIndex; + final double heightPercentage = + pdfDocument.pages[_viewId].size.height / height; + final PdfPage page = pdfDocument.pages[pageIndex]; + final int length = page.annotations.count; + for (int index = 0; index < length; index++) { + if (page.annotations[index] is PdfDocumentLinkAnnotation) { + _documentLinkAnnotation = page.annotations[index]; + + 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; + final double destinationPageOffset = + _documentLinkAnnotation.destination.location.dy / + heightPercentage; + _totalPageOffset = pdfPages[destinationPageIndex].pageOffset + + destinationPageOffset; + _viewId = pageIndex; + + /// 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; + } + } + } + } + } + } + + /// Handles the tap down event + void _handleTapDown(TapDownDetails details) { + _tapDetails = details.localPosition; + } + + /// Handles the long press started event. + void _handleLongPressStart(LongPressStartDetails details) { + if (enableTextSelection) { + if (_textSelectionHelper.selectionEnabled) { + clearSelection(); + } + _longPressed = true; + _textSelectionHelper.viewId = pageIndex; + markNeedsPaint(); + } + } + + /// Handles the Drag start event. + void _handleDragStart(DragStartDetails details) { + if (_textSelectionHelper.selectionEnabled) { + final bool isStartDragPossible = _checkStartBubblePosition(); + final bool isEndDragPossible = _checkEndBubblePosition(); + if (isStartDragPossible) { + _startBubbleDragging = true; + onTextSelectionDragStarted(); + } else if (isEndDragPossible) { + _endBubbleDragging = true; + onTextSelectionDragStarted(); + } + } + } + + /// Handles the drag update event. + void _handleDragUpdate(DragUpdateDetails details) { + if (_textSelectionHelper.selectionEnabled) { + _dragDetails = details.localPosition; + if (_startBubbleDragging) { + _startBubbleTapX = details.localPosition.dx; + markNeedsPaint(); + if (onTextSelectionChanged != null) { + onTextSelectionChanged(PdfTextSelectionChangedDetails(null, null)); + } + } else if (_endBubbleDragging) { + _endBubbleTapX = details.localPosition.dx; + markNeedsPaint(); + if (onTextSelectionChanged != null) { + onTextSelectionChanged(PdfTextSelectionChangedDetails(null, null)); + } + } + } + } + + /// Handles the drag end event. + void _handleDragEnd(DragEndDetails details) { + if (_textSelectionHelper.selectionEnabled) { + if (_startBubbleDragging) { + _startBubbleDragging = false; + onTextSelectionDragEnded(); + if (onTextSelectionChanged != null) { + onTextSelectionChanged(PdfTextSelectionChangedDetails( + _textSelectionHelper.copiedText, + _textSelectionHelper.globalSelectedRegion)); + } + } + if (_endBubbleDragging) { + _endBubbleDragging = false; + onTextSelectionDragEnded(); + if (onTextSelectionChanged != null) { + onTextSelectionChanged(PdfTextSelectionChangedDetails( + _textSelectionHelper.copiedText, + _textSelectionHelper.globalSelectedRegion)); + } + } + } + } + + /// Handles the drag down event. + void _handleDragDown(DragDownDetails details) { + _dragDownDetails = details.localPosition; + } + + /// Triggers when scrolling of page is started. + void scrollStarted() { + if (_textSelectionHelper.selectionEnabled) { + if (onTextSelectionChanged != null) { + onTextSelectionChanged(PdfTextSelectionChangedDetails(null, null)); + } + } + } + + /// Triggers when scrolling of page is ended. + void scrollEnded() { + if (_textSelectionHelper.selectionEnabled) { + final double _heightPercentage = _textSelectionHelper.heightPercentage; + // addPostFrameCallback triggers after paint method is called to update the globalSelectedRegion values. + // So that context menu position updating properly while changing orientation and double tap zoom. + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (onTextSelectionChanged != null && + ((pdfPages[_textSelectionHelper.viewId + 1].pageOffset + + _textSelectionHelper.endBubbleY / + _heightPercentage >= + scrollController.offset && + pdfPages[_textSelectionHelper.viewId + 1].pageOffset + + _textSelectionHelper.endBubbleY / + _heightPercentage <= + scrollController.offset + + scrollController.position.viewportDimension) || + (pdfPages[_textSelectionHelper.viewId + 1].pageOffset + + _textSelectionHelper.firstGlyphOffset.dy / + _heightPercentage >= + scrollController.offset && + pdfPages[_textSelectionHelper.viewId + 1].pageOffset + + _textSelectionHelper.firstGlyphOffset.dy / + _heightPercentage <= + scrollController.offset + + scrollController.position.viewportDimension))) { + onTextSelectionChanged(PdfTextSelectionChangedDetails( + _textSelectionHelper.copiedText, + _textSelectionHelper.globalSelectedRegion)); + } + }); + } + } + + /// Check the tap position same as the start bubble position. + bool _checkStartBubblePosition() { + if (_textSelectionHelper.selectionEnabled && _dragDownDetails != null) { + final double startBubbleX = _textSelectionHelper.startBubbleX / + _textSelectionHelper.heightPercentage; + final double startBubbleY = _textSelectionHelper.startBubbleY / + _textSelectionHelper.heightPercentage; + if (_dragDownDetails.dx >= + startBubbleX - (_bubbleSize * _maximumZoomLevel) && + _dragDownDetails.dx <= startBubbleX && + _dragDownDetails.dy >= startBubbleY - _bubbleSize && + _dragDownDetails.dy <= startBubbleY + _bubbleSize) { + return true; + } + } + return false; + } + + /// Check the tap position same as the end bubble position. + bool _checkEndBubblePosition() { + if (_textSelectionHelper.selectionEnabled && _dragDownDetails != null) { + final double endBubbleX = _textSelectionHelper.endBubbleX / + _textSelectionHelper.heightPercentage; + final double endBubbleY = _textSelectionHelper.endBubbleY / + _textSelectionHelper.heightPercentage; + if (_dragDownDetails.dx >= endBubbleX && + _dragDownDetails.dx <= + endBubbleX + (_bubbleSize * _maximumZoomLevel) && + _dragDownDetails.dy >= endBubbleY - _bubbleSize && + _dragDownDetails.dy <= endBubbleY + _bubbleSize) { + return true; + } + } + return false; + } + + void _sortTextLines() { + for (int textLineIndex = 0; + textLineIndex < _textSelectionHelper.textLines.length; + textLineIndex++) { + for (int index = textLineIndex + 1; + index < _textSelectionHelper.textLines.length; + index++) { + if (_textSelectionHelper.textLines[textLineIndex].bounds.bottom > + _textSelectionHelper.textLines[index].bounds.bottom) { + final TextLine textLine = + _textSelectionHelper.textLines[textLineIndex]; + _textSelectionHelper.textLines[textLineIndex] = + _textSelectionHelper.textLines[index]; + _textSelectionHelper.textLines[index] = textLine; + } + } + } + } + + /// Ensuring history for text selection. + void _ensureHistoryEntry() { + Future.delayed(Duration.zero, () async { + if (onTextSelectionChanged != null) { + onTextSelectionChanged(PdfTextSelectionChangedDetails( + _textSelectionHelper.copiedText, + _textSelectionHelper.globalSelectedRegion)); + } + if (_textSelectionHelper.historyEntry == null) { + final ModalRoute route = ModalRoute.of(context); + if (route != null) { + _textSelectionHelper.historyEntry = + LocalHistoryEntry(onRemove: _handleHistoryEntryRemoved); + route.addLocalHistoryEntry(_textSelectionHelper.historyEntry); + } + } + }); + } + + /// Remove history for Text Selection. + void _handleHistoryEntryRemoved() { + if (textCollection != null && _textSelectionHelper.historyEntry != null) { + Navigator.of(context).maybePop(); + } + _textSelectionHelper.historyEntry = null; + clearSelection(); + } + + /// clears Text Selection. + bool clearSelection() { + final bool clearTextSelection = !_textSelectionHelper.selectionEnabled; + if (_textSelectionHelper.selectionEnabled) { + _textSelectionHelper.selectionEnabled = false; + if (_textSelectionHelper.historyEntry != null && + Navigator.canPop(context)) { + _textSelectionHelper.historyEntry = null; + Navigator.of(context).maybePop(); + } + markNeedsPaint(); + if (onTextSelectionChanged != null) { + onTextSelectionChanged(PdfTextSelectionChangedDetails(null, null)); + } + dispose(); + } + return clearTextSelection; + } + + /// Dispose the canvas. + void dispose() { + _textSelectionHelper.textLines = null; + _textSelectionHelper.viewId = null; + _textSelectionHelper.copiedText = null; + _textSelectionHelper.globalSelectedRegion = null; + _textSelectionHelper.firstGlyphOffset = null; + _textSelectionHelper.startBubbleX = null; + _textSelectionHelper.startBubbleY = null; + _textSelectionHelper.endBubbleX = null; + _textSelectionHelper.endBubbleY = null; + _textSelectionHelper.startBubbleLine = null; + _textSelectionHelper.endBubbleLine = null; + _textSelectionHelper.heightPercentage = null; + } + + /// Draw the start bubble. + void _drawStartBubble( + Canvas canvas, Paint bubblePaint, Offset startBubbleOffset) { + canvas.drawRRect( + RRect.fromLTRBAndCorners( + startBubbleOffset.dx - (_bubbleSize / _zoomPercentage), + startBubbleOffset.dy, + startBubbleOffset.dx, + startBubbleOffset.dy + (_bubbleSize / _zoomPercentage), + topLeft: Radius.circular(10.0), + topRight: Radius.circular(1.0), + bottomRight: Radius.circular(10.0), + bottomLeft: Radius.circular(10.0)), + bubblePaint); + } + + /// Draw the end bubble. + void _drawEndBubble( + Canvas canvas, Paint bubblePaint, Offset endBubbleOffset) { + canvas.drawRRect( + RRect.fromLTRBAndCorners( + endBubbleOffset.dx, + endBubbleOffset.dy, + endBubbleOffset.dx + (_bubbleSize / _zoomPercentage), + endBubbleOffset.dy + (_bubbleSize / _zoomPercentage), + topLeft: Radius.circular(1.0), + topRight: Radius.circular(10.0), + bottomRight: Radius.circular(10.0), + bottomLeft: Radius.circular(10.0)), + bubblePaint); + } + + /// Draw the Rect for selected text. + void _drawTextRect(Canvas canvas, Paint textPaint, Rect textRectOffset) { + canvas.drawRect(textRectOffset, textPaint); + } + + @override + void paint(PaintingContext context, Offset offset) { + final Canvas canvas = context.canvas; + + if (pageIndex == _viewId) { + if (_isTOCTapped) { + final double heightPercentage = + pdfDocument.pages[_viewId].size.height / height; + final Paint wordPaint = Paint() + ..color = Color.fromRGBO(228, 238, 244, 1); + canvas.drawRect( + 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 { + scrollController.jumpTo(_totalPageOffset); + }); + _isTOCTapped = false; + } + } + if (textCollection != null && !_textSelectionHelper.selectionEnabled) { + final Paint searchTextPaint = Paint() + ..color = searchTextHighlightColor.withOpacity(0.3); + + final Paint currentInstancePaint = Paint() + ..color = searchTextHighlightColor.withOpacity(0.6); + + int _pageNumber; + 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), + searchTextPaint); + } + + if (pdfTextSearchResult != null && + textCollection[pdfTextSearchResult.currentInstanceIndex - 1] + .pageIndex == + pageIndex) { + if (textCollection[pdfTextSearchResult.currentInstanceIndex - 1] + .pageIndex != + _pageNumber) { + canvas.drawRect( + offset.translate( + textCollection[ + pdfTextSearchResult.currentInstanceIndex - + 1] + .bounds + .left / + _heightPercentage, + textCollection[ + pdfTextSearchResult.currentInstanceIndex - + 1] + .bounds + .top / + _heightPercentage) & + Size( + textCollection[ + pdfTextSearchResult.currentInstanceIndex - + 1] + .bounds + .width / + _heightPercentage, + textCollection[ + pdfTextSearchResult.currentInstanceIndex - + 1] + .bounds + .height / + _heightPercentage), + currentInstancePaint); + _pageNumber = + textCollection[pdfTextSearchResult.currentInstanceIndex - 1] + .pageIndex; + } + } else if (item.pageIndex > pageIndex) { + break; + } + } + } + + final Paint textPaint = Paint() + ..color = Theme.of(this.context).textSelectionColor.withOpacity(0.5); + final Paint bubblePaint = Paint() + ..color = Theme.of(this.context).textSelectionHandleColor; + _zoomPercentage = pdfViewerController.zoomLevel > _maximumZoomLevel + ? _maximumZoomLevel + : pdfViewerController.zoomLevel; + if (_longPressed) { + final double _heightPercentage = + pdfDocument.pages[_textSelectionHelper.viewId].size.height / height; + _textSelectionHelper.heightPercentage = _heightPercentage; + _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++) { + final TextWord textWord = textWordCollection[wordIndex]; + final Rect wordBounds = textWord.bounds; + if (wordBounds.contains(_tapDetails * _heightPercentage)) { + _textSelectionHelper.startBubbleLine = + _textSelectionHelper.textLines[textLineIndex]; + _textSelectionHelper.copiedText = textWord.text; + _textSelectionHelper.endBubbleLine = + _textSelectionHelper.textLines[textLineIndex]; + _startBubbleTapX = + textWord.bounds.bottomLeft.dx / _heightPercentage; + _textSelectionHelper.startBubbleY = textWord.bounds.bottomLeft.dy; + _endBubbleTapX = textWord.bounds.bottomRight.dx / _heightPercentage; + _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); + _drawTextRect(canvas, textPaint, textRectOffset); + final Offset startBubbleOffset = offset.translate( + 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); + _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.firstGlyphOffset = + Offset(textWord.bounds.topLeft.dx, textWord.bounds.topLeft.dy); + _textSelectionHelper.selectionEnabled = true; + _ensureHistoryEntry(); + _sortTextLines(); + } + } + } + _longPressed = false; + } else if (_textSelectionHelper.selectionEnabled && + pageIndex == _textSelectionHelper.viewId) { + final double _heightPercentage = + pdfDocument.pages[_textSelectionHelper.viewId].size.height / height; + _textSelectionHelper.heightPercentage = _heightPercentage; + if (_startBubbleDragging) { + for (int textLineIndex = 0; + textLineIndex < _textSelectionHelper.textLines.length; + 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; + } + for (int wordIndex = 0; + wordIndex < + _textSelectionHelper.startBubbleLine.wordCollection.length; + wordIndex++) { + final TextWord textWord = + _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) && + _startBubbleTapX <= + (textGlyph.bounds.bottomRight.dx / _heightPercentage)) { + _textSelectionHelper.startBubbleX = + textGlyph.bounds.bottomLeft.dx; + _textSelectionHelper.firstGlyphOffset = + textGlyph.bounds.topLeft; + } + } + } + if (_startBubbleTapX < + (_textSelectionHelper.startBubbleLine.bounds.bottomLeft.dx / + _heightPercentage)) { + _textSelectionHelper.startBubbleX = + (_textSelectionHelper.startBubbleLine.bounds.bottomLeft.dx); + _textSelectionHelper.firstGlyphOffset = + _textSelectionHelper.startBubbleLine.bounds.topLeft; + } + if (_startBubbleTapX >= + (_textSelectionHelper.startBubbleLine.bounds.bottomRight.dx / + _heightPercentage)) { + _textSelectionHelper.startBubbleX = (_textSelectionHelper + .startBubbleLine + .wordCollection + .last + .glyphs + .last + .bounds + .bottomLeft + .dx); + _textSelectionHelper.firstGlyphOffset = _textSelectionHelper + .startBubbleLine.wordCollection.last.glyphs.last.bounds.topLeft; + } + 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; + 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.firstGlyphOffset = + textGlyph.bounds.topLeft; + break; + } + } + } + } + } + } else if (_endBubbleDragging) { + for (int textLineIndex = 0; + textLineIndex < _textSelectionHelper.textLines.length; + textLineIndex++) { + final TextLine line = _textSelectionHelper.textLines[textLineIndex]; + if (_dragDetails != null && + _dragDetails.dy >= + (_textSelectionHelper.startBubbleLine.bounds.top / + _heightPercentage) && + _dragDetails.dy >= (line.bounds.topLeft.dy / _heightPercentage)) { + _textSelectionHelper.endBubbleLine = line; + _textSelectionHelper.endBubbleY = line.bounds.bottomRight.dy; + } + for (int wordIndex = 0; + wordIndex < + _textSelectionHelper.endBubbleLine.wordCollection.length; + wordIndex++) { + final TextWord textWord = + _textSelectionHelper.endBubbleLine.wordCollection[wordIndex]; + for (int glyphIndex = 0; + glyphIndex < textWord.glyphs.length; + glyphIndex++) { + final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; + if (_endBubbleTapX >= + (textGlyph.bounds.bottomLeft.dx / _heightPercentage) && + _endBubbleTapX <= + (textGlyph.bounds.bottomRight.dx / _heightPercentage)) { + _textSelectionHelper.endBubbleX = + 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; + wordIndex < + _textSelectionHelper.endBubbleLine.wordCollection.length; + 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; + } + } + } + } + } + } + _textSelectionHelper.copiedText = ''; + 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++) { + final TextWord textWord = textWordCollection[wordIndex]; + for (int glyphIndex = 0; + glyphIndex < textWord.glyphs.length; + glyphIndex++) { + final TextGlyph glyph = textWord.glyphs[glyphIndex]; + if (glyph.bounds.bottom / _heightPercentage == + _textSelectionHelper.startBubbleLine.bounds.bottom / + _heightPercentage) { + if ((glyph.bounds.bottomCenter.dx / _heightPercentage >= + _textSelectionHelper.startBubbleX / + _heightPercentage && + glyph.bounds.bottomCenter.dx / _heightPercentage < + _textSelectionHelper.endBubbleX / + _heightPercentage) || + (glyph.bounds.bottomCenter.dx / _heightPercentage >= + _textSelectionHelper.startBubbleX / + _heightPercentage && + _textSelectionHelper.endBubbleY / _heightPercentage > + glyph.bounds.bottom / _heightPercentage)) { + _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); + _drawTextRect(canvas, textPaint, textRectOffset); + } + } else if ((glyph.bounds.bottomLeft.dy / _heightPercentage >= + _textSelectionHelper.startBubbleY / _heightPercentage && + _textSelectionHelper.endBubbleX / _heightPercentage > + glyph.bounds.bottomCenter.dx / _heightPercentage && + _textSelectionHelper.endBubbleY / _heightPercentage > + glyph.bounds.top / _heightPercentage) || + (_textSelectionHelper.endBubbleY / _heightPercentage > + glyph.bounds.bottom / _heightPercentage && + glyph.bounds.bottomLeft.dy / _heightPercentage >= + _textSelectionHelper.startBubbleY / + _heightPercentage)) { + _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); + _drawTextRect(canvas, textPaint, textRectOffset); + } + } + } + } + final Offset startBubbleOffset = offset.translate( + _textSelectionHelper.startBubbleX / _heightPercentage, + _textSelectionHelper.startBubbleY / _heightPercentage); + final Offset endBubbleOffset = offset.translate( + _textSelectionHelper.endBubbleX / _heightPercentage, + _textSelectionHelper.endBubbleY / _heightPercentage); + _drawStartBubble(canvas, bubblePaint, startBubbleOffset); + _drawEndBubble(canvas, bubblePaint, endBubbleOffset); + _textSelectionHelper.globalSelectedRegion = Rect.fromPoints( + localToGlobal(Offset( + _textSelectionHelper.firstGlyphOffset.dx / _heightPercentage, + _textSelectionHelper.firstGlyphOffset.dy / _heightPercentage)), + localToGlobal(Offset( + _textSelectionHelper.endBubbleX / _heightPercentage, + _textSelectionHelper.endBubbleY / _heightPercentage))); + } + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart index 282124221..115b1ff13 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart @@ -1,14 +1,10 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_core/localizations.dart'; -/// Height of the scroll head. +/// Height of the ScrollHead. const double kPdfScrollHeadHeight = 32.0; -/// Height of the pagination text field. -const double _kPdfPaginationTextFieldWidth = 328.0; - /// A material design scroll head. /// /// Scroll head is similar to [Scrollbar] but it has current page number @@ -17,33 +13,11 @@ const double _kPdfPaginationTextFieldWidth = 328.0; @immutable class ScrollHead extends StatefulWidget { /// Constructor for ScrollHead. - ScrollHead( - {this.canShowPaginationDialog, - this.scrollHeadOffset, - this.onScrollHeadDragStart, - this.onScrollHeadDragUpdate, - this.onScrollHeadDragEnd, - this.pdfViewerController}); + ScrollHead(this.scrollHeadOffset, this.pdfViewerController); /// Position of the [ScrollHead] in [SfPdfViewer]. final double scrollHeadOffset; - /// A pointer has contacted the screen with a scroll head and has begun to - /// move vertically. - final GestureDragStartCallback onScrollHeadDragStart; - - /// A pointer that is in contact with the screen with a scroll head and - /// moving vertically has moved in the vertical direction. - final GestureDragUpdateCallback onScrollHeadDragUpdate; - - /// A pointer that was previously in contact with the screen with a scroll - /// head and moving vertically is no longer in contact with the screen and - /// was moving at a specific velocity when it stopped contacting the screen. - final GestureDragEndCallback onScrollHeadDragEnd; - - /// Indicates whether page navigation dialog must be shown or not. - final bool canShowPaginationDialog; - /// PdfViewer controller of PdfViewer final PdfViewerController pdfViewerController; @@ -53,218 +27,71 @@ class ScrollHead extends StatefulWidget { /// State for [ScrollHead] class _ScrollHeadState extends State { - final TextEditingController _textFieldController = TextEditingController(); - final _formKey = GlobalKey(); - final FocusScopeNode _focusScopeNode = FocusScopeNode(); SfPdfViewerThemeData _pdfViewerThemeData; - SfLocalizations _localizations; @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); - _localizations = SfLocalizations.of(context); super.didChangeDependencies(); } @override void dispose() { _pdfViewerThemeData = null; - _localizations = null; super.dispose(); } @override Widget build(BuildContext context) { - return Align( - alignment: Alignment.topRight, - child: GestureDetector( - onVerticalDragStart: widget.onScrollHeadDragStart, - onVerticalDragUpdate: widget.onScrollHeadDragUpdate, - onVerticalDragEnd: widget.onScrollHeadDragEnd, - onTap: () { - _textFieldController.clear(); - if (!FocusScope.of(context).hasPrimaryFocus) { - FocusScope.of(context).unfocus(); - } - if (widget.canShowPaginationDialog) { - _showPaginationDialog(); - } - }, - child: Container( - margin: EdgeInsets.only(top: widget.scrollHeadOffset), - child: Stack( - children: [ - Material( - child: Container( - decoration: BoxDecoration( - color: _pdfViewerThemeData.scrollHeadStyle.backgroundColor, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(kPdfScrollHeadHeight), - bottomLeft: Radius.circular(kPdfScrollHeadHeight), - ), - boxShadow: [ - BoxShadow( - color: Color.fromRGBO(0, 0, 0, 0.14), - blurRadius: 2, - offset: Offset(0, 0), - ), - BoxShadow( - color: Color.fromRGBO(0, 0, 0, 0.12), - blurRadius: 2, - offset: Offset(0, 2), - ), - BoxShadow( - color: Color.fromRGBO(0, 0, 0, 0.2), - blurRadius: 3, - offset: Offset(0, 1), - ), - ], - ), - constraints: BoxConstraints.tightFor( - width: kPdfScrollHeadHeight, - height: kPdfScrollHeadHeight), - ), + return Container( + margin: EdgeInsets.only(top: widget.scrollHeadOffset), + child: Stack( + children: [ + Material( + child: Container( + decoration: BoxDecoration( + color: _pdfViewerThemeData.scrollHeadStyle.backgroundColor, borderRadius: BorderRadius.only( topLeft: Radius.circular(kPdfScrollHeadHeight), bottomLeft: Radius.circular(kPdfScrollHeadHeight), ), - ), - Positioned.fill( - child: Align( - alignment: Alignment.center, - child: Text( - '${widget.pdfViewerController.pageNumber}', - style: - _pdfViewerThemeData.scrollHeadStyle.pageNumberTextStyle, + boxShadow: [ + BoxShadow( + color: Color.fromRGBO(0, 0, 0, 0.14), + blurRadius: 2, + offset: Offset(0, 0), ), - ), + BoxShadow( + color: Color.fromRGBO(0, 0, 0, 0.12), + blurRadius: 2, + offset: Offset(0, 2), + ), + BoxShadow( + color: Color.fromRGBO(0, 0, 0, 0.2), + blurRadius: 3, + offset: Offset(0, 1), + ), + ], ), - ], - ), - ), - ), - ); - } - - /// Show the pagination dialog box - Future _showPaginationDialog() async { - return showDialog( - context: context, - barrierDismissible: true, - builder: (BuildContext context) { - final orientation = MediaQuery.of(context).orientation; - return AlertDialog( - scrollable: true, - insetPadding: EdgeInsets.all(0), - contentPadding: orientation == Orientation.portrait - ? EdgeInsets.all(24) - : EdgeInsets.only(top: 0, right: 24, left: 24, bottom: 0), - buttonPadding: orientation == Orientation.portrait - ? EdgeInsets.all(8) - : EdgeInsets.all(4), - backgroundColor: - _pdfViewerThemeData.paginationDialogStyle.backgroundColor, - title: Text( - _localizations.pdfGoToPageLabel, - style: _pdfViewerThemeData.paginationDialogStyle.headerTextStyle, + constraints: BoxConstraints.tightFor( + width: kPdfScrollHeadHeight, height: kPdfScrollHeadHeight), ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4.0))), - content: SingleChildScrollView(child: _paginationTextField()), - actions: [ - FlatButton( - child: Text( - _localizations.pdfPaginationDialogCancelLabel, - style: _pdfViewerThemeData - .paginationDialogStyle.cancelTextStyle.color == - null - ? _pdfViewerThemeData - .paginationDialogStyle.cancelTextStyle - .copyWith(color: Theme.of(context).primaryColor) - : _pdfViewerThemeData - .paginationDialogStyle.cancelTextStyle, - ), - onPressed: () { - _textFieldController.clear(); - Navigator.of(context).pop(); - }, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(kPdfScrollHeadHeight), + bottomLeft: Radius.circular(kPdfScrollHeadHeight), + ), + ), + Positioned.fill( + child: Align( + alignment: Alignment.center, + child: Text( + '${widget.pdfViewerController.pageNumber}', + style: _pdfViewerThemeData.scrollHeadStyle.pageNumberTextStyle, ), - FlatButton( - child: Text( - _localizations.pdfPaginationDialogOkLabel, - style: _pdfViewerThemeData - .paginationDialogStyle.okTextStyle.color == - null - ? _pdfViewerThemeData.paginationDialogStyle.okTextStyle - .copyWith(color: Theme.of(context).primaryColor) - : _pdfViewerThemeData.paginationDialogStyle.okTextStyle, - ), - onPressed: () { - _handlePageNumberValidation(); - }, - ) - ], - ); - }); - } - - /// A material design Text field for pagination dialog box. - Widget _paginationTextField() { - return Form( - key: _formKey, - child: Container( - width: _kPdfPaginationTextFieldWidth, - child: TextFormField( - style: _pdfViewerThemeData.paginationDialogStyle.inputFieldTextStyle, - focusNode: _focusScopeNode, - decoration: InputDecoration( - isDense: true, - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Theme.of(context).primaryColor), ), - contentPadding: const EdgeInsets.symmetric(vertical: 6), - hintText: _localizations.pdfEnterPageNumberLabel, - hintStyle: _pdfViewerThemeData.paginationDialogStyle.hintTextStyle, - counterText: - '${widget.pdfViewerController.pageNumber}/${widget.pdfViewerController.pageCount}', - counterStyle: - _pdfViewerThemeData.paginationDialogStyle.pageInfoTextStyle, - errorStyle: - _pdfViewerThemeData.paginationDialogStyle.validationTextStyle, ), - keyboardType: TextInputType.number, - enableInteractiveSelection: false, - controller: _textFieldController, - autofocus: true, - onEditingComplete: _handlePageNumberValidation, - onFieldSubmitted: (String value) { - _handlePageNumberValidation(); - }, - // ignore: missing_return - validator: (value) { - try { - final int index = int.parse(value); - if (index <= 0 || index > widget.pdfViewerController.pageCount) { - _textFieldController.clear(); - return _localizations.pdfInvalidPageNumberLabel; - } - } on Exception { - _textFieldController.clear(); - return _localizations.pdfInvalidPageNumberLabel; - } - }, - ), + ], ), ); } - - /// Validates the page number entered in text field. - void _handlePageNumberValidation() { - if (_formKey.currentState.validate()) { - final int index = int.parse(_textFieldController.text); - _textFieldController.clear(); - Navigator.of(context).pop(); - widget.pdfViewerController.jumpToPage(index); - } - } } 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 new file mode 100644 index 000000000..c6a089e19 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head_overlay.dart @@ -0,0 +1,222 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/control/scroll_head.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; + +/// Height of the scroll head. +const double kPdfScrollHeadHeight = 32.0; + +/// Height of the pagination text field. +const double _kPdfPaginationTextFieldWidth = 328.0; + +/// [ScrollHeadOverlay] which contains scrollHead +@immutable +class ScrollHeadOverlay extends StatefulWidget { + /// Constructor for ScrollHeadOverlay. + ScrollHeadOverlay( + {this.canShowPaginationDialog, + this.scrollHeadOffset, + this.onScrollHeadDragStart, + this.onScrollHeadDragUpdate, + this.onScrollHeadDragEnd, + this.pdfViewerController}); + + /// Position of the [ScrollHeadOverlay] in [SfPdfViewer]. + final double scrollHeadOffset; + + /// A pointer has contacted the screen with a scroll head and has begun to + /// move vertically. + final GestureDragStartCallback onScrollHeadDragStart; + + /// A pointer that is in contact with the screen with a scroll head and + /// moving vertically has moved in the vertical direction. + final GestureDragUpdateCallback onScrollHeadDragUpdate; + + /// A pointer that was previously in contact with the screen with a scroll + /// head and moving vertically is no longer in contact with the screen and + /// was moving at a specific velocity when it stopped contacting the screen. + final GestureDragEndCallback onScrollHeadDragEnd; + + /// Indicates whether page navigation dialog must be shown or not. + final bool canShowPaginationDialog; + + /// PdfViewer controller of PdfViewer + final PdfViewerController pdfViewerController; + + @override + _ScrollHeadOverlayState createState() => _ScrollHeadOverlayState(); +} + +/// State for [ScrollHeadOverlay] +class _ScrollHeadOverlayState extends State { + final TextEditingController _textFieldController = TextEditingController(); + final _formKey = GlobalKey(); + final FocusScopeNode _focusScopeNode = FocusScopeNode(); + SfPdfViewerThemeData _pdfViewerThemeData; + SfLocalizations _localizations; + + @override + void didChangeDependencies() { + _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _localizations = SfLocalizations.of(context); + super.didChangeDependencies(); + } + + @override + void dispose() { + _pdfViewerThemeData = null; + _localizations = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.topRight, + child: GestureDetector( + onVerticalDragStart: widget.onScrollHeadDragStart, + onVerticalDragUpdate: widget.onScrollHeadDragUpdate, + onVerticalDragEnd: widget.onScrollHeadDragEnd, + onTap: () { + _textFieldController.clear(); + if (!FocusScope.of(context).hasPrimaryFocus) { + FocusScope.of(context).unfocus(); + } + if (widget.canShowPaginationDialog) { + _showPaginationDialog(); + } + }, + child: ScrollHead(widget.scrollHeadOffset, widget.pdfViewerController), + ), + ); + } + + /// Clears the Text Selection. + Future _clearSelection() async { + return widget.pdfViewerController.clearSelection(); + } + + /// Show the pagination dialog box + Future _showPaginationDialog() async { + await _clearSelection(); + return showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + final orientation = MediaQuery.of(context).orientation; + return AlertDialog( + scrollable: true, + insetPadding: EdgeInsets.all(0), + contentPadding: orientation == Orientation.portrait + ? EdgeInsets.all(24) + : EdgeInsets.only(top: 0, right: 24, left: 24, bottom: 0), + buttonPadding: orientation == Orientation.portrait + ? EdgeInsets.all(8) + : EdgeInsets.all(4), + backgroundColor: + _pdfViewerThemeData.paginationDialogStyle.backgroundColor, + title: Text( + _localizations.pdfGoToPageLabel, + style: _pdfViewerThemeData.paginationDialogStyle.headerTextStyle, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0))), + content: SingleChildScrollView(child: _paginationTextField()), + actions: [ + FlatButton( + child: Text( + _localizations.pdfPaginationDialogCancelLabel, + style: _pdfViewerThemeData + .paginationDialogStyle.cancelTextStyle.color == + null + ? _pdfViewerThemeData + .paginationDialogStyle.cancelTextStyle + .copyWith(color: Theme.of(context).primaryColor) + : _pdfViewerThemeData + .paginationDialogStyle.cancelTextStyle, + ), + onPressed: () { + _textFieldController.clear(); + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text( + _localizations.pdfPaginationDialogOkLabel, + style: _pdfViewerThemeData + .paginationDialogStyle.okTextStyle.color == + null + ? _pdfViewerThemeData.paginationDialogStyle.okTextStyle + .copyWith(color: Theme.of(context).primaryColor) + : _pdfViewerThemeData.paginationDialogStyle.okTextStyle, + ), + onPressed: () { + _handlePageNumberValidation(); + }, + ) + ], + ); + }); + } + + /// A material design Text field for pagination dialog box. + Widget _paginationTextField() { + return Form( + key: _formKey, + child: Container( + width: _kPdfPaginationTextFieldWidth, + child: TextFormField( + style: _pdfViewerThemeData.paginationDialogStyle.inputFieldTextStyle, + focusNode: _focusScopeNode, + decoration: InputDecoration( + isDense: true, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Theme.of(context).primaryColor), + ), + contentPadding: const EdgeInsets.symmetric(vertical: 6), + hintText: _localizations.pdfEnterPageNumberLabel, + hintStyle: _pdfViewerThemeData.paginationDialogStyle.hintTextStyle, + counterText: + '${widget.pdfViewerController.pageNumber}/${widget.pdfViewerController.pageCount}', + counterStyle: + _pdfViewerThemeData.paginationDialogStyle.pageInfoTextStyle, + errorStyle: + _pdfViewerThemeData.paginationDialogStyle.validationTextStyle, + ), + keyboardType: TextInputType.number, + enableInteractiveSelection: false, + controller: _textFieldController, + autofocus: true, + onEditingComplete: _handlePageNumberValidation, + onFieldSubmitted: (String value) { + _handlePageNumberValidation(); + }, + // ignore: missing_return + validator: (value) { + try { + final int index = int.parse(value); + if (index <= 0 || index > widget.pdfViewerController.pageCount) { + _textFieldController.clear(); + return _localizations.pdfInvalidPageNumberLabel; + } + } on Exception { + _textFieldController.clear(); + return _localizations.pdfInvalidPageNumberLabel; + } + }, + ), + ), + ); + } + + /// Validates the page number entered in text field. + void _handlePageNumberValidation() { + if (_formKey.currentState.validate()) { + final int index = int.parse(_textFieldController.text); + _textFieldController.clear(); + Navigator.of(context).pop(); + widget.pdfViewerController.jumpToPage(index); + } + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart index f963fd097..dcc63f3db 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; +import 'package:async/async.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -17,7 +18,11 @@ import 'common/pdf_provider.dart'; import 'control/enums.dart'; import 'control/pagination.dart'; import 'control/pdfviewer_callback_details.dart'; -import 'control/scroll_head.dart'; +import 'control/scroll_head_overlay.dart'; + +/// Signature for [SfPdfViewer.onTextSelectionChanged] callback. +typedef PdfTextSelectionChangedCallback = void Function( + PdfTextSelectionChangedDetails details); /// Signature for [SfPdfViewer.onDocumentLoaded] callback. typedef PdfDocumentLoadedCallback = void Function( @@ -97,20 +102,32 @@ class SfPdfViewer extends StatefulWidget { {Key key, AssetBundle bundle, this.canShowScrollHead = true, + this.pageSpacing = 4, this.controller, this.onZoomLevelChanged, this.canShowScrollStatus = true, this.onPageChanged, this.onDocumentLoaded, this.enableDoubleTapZooming = true, + this.enableTextSelection = true, + this.onTextSelectionChanged, this.onDocumentLoadFailed, - this.canShowPaginationDialog = true}) + this.enableDocumentLinkAnnotation = true, + this.canShowPaginationDialog = true, + this.initialScrollOffset = Offset.zero, + this.initialZoomLevel = 1, + this.searchTextHighlightColor = const Color(0xFFE56E00)}) : _provider = AssetPdf(name, bundle), assert(canShowScrollHead != null), assert(canShowScrollHead != null), assert(canShowScrollStatus != null), assert(enableDoubleTapZooming != null), assert(canShowPaginationDialog != null), + assert(initialZoomLevel != null), + assert(initialScrollOffset != null), + assert(pageSpacing != null && pageSpacing >= 0), + assert(enableDocumentLinkAnnotation != null), + assert(searchTextHighlightColor != null), super(key: key); /// Creates a widget that displays the PDF document obtained from the network. @@ -138,19 +155,31 @@ class SfPdfViewer extends StatefulWidget { SfPdfViewer.network(String src, {Key key, this.canShowScrollHead = true, + this.pageSpacing = 4, this.controller, this.onZoomLevelChanged, this.canShowScrollStatus = true, this.onPageChanged, this.enableDoubleTapZooming = true, + this.enableTextSelection = true, + this.onTextSelectionChanged, this.onDocumentLoaded, this.onDocumentLoadFailed, - this.canShowPaginationDialog = true}) + this.enableDocumentLinkAnnotation = true, + this.canShowPaginationDialog = true, + this.initialScrollOffset = Offset.zero, + this.initialZoomLevel = 1, + this.searchTextHighlightColor = const Color(0xFFE56E00)}) : _provider = NetworkPdf(src), assert(canShowScrollHead != null), assert(canShowScrollStatus != null), + assert(initialZoomLevel != null), + assert(initialScrollOffset != null), assert(enableDoubleTapZooming != null), assert(canShowPaginationDialog != null), + assert(pageSpacing != null && pageSpacing >= 0), + assert(enableDocumentLinkAnnotation != null), + assert(searchTextHighlightColor != null), super(key: key); /// Creates a widget that displays the PDF document obtained from [Uint8List]. @@ -177,19 +206,31 @@ class SfPdfViewer extends StatefulWidget { SfPdfViewer.memory(Uint8List bytes, {Key key, this.canShowScrollHead = true, + this.pageSpacing = 4, this.controller, this.onZoomLevelChanged, this.canShowScrollStatus = true, this.onPageChanged, this.enableDoubleTapZooming = true, + this.enableTextSelection = true, + this.onTextSelectionChanged, this.onDocumentLoaded, this.onDocumentLoadFailed, - this.canShowPaginationDialog = true}) + this.enableDocumentLinkAnnotation = true, + this.canShowPaginationDialog = true, + this.initialScrollOffset = Offset.zero, + this.initialZoomLevel = 1, + this.searchTextHighlightColor = const Color(0xFFE56E00)}) : _provider = MemoryPdf(bytes), assert(canShowScrollHead != null), assert(canShowScrollStatus != null), + assert(initialZoomLevel != null), + assert(initialScrollOffset != null), assert(enableDoubleTapZooming != null), assert(canShowPaginationDialog != null), + assert(pageSpacing != null && pageSpacing >= 0), + assert(enableDocumentLinkAnnotation != null), + assert(searchTextHighlightColor != null), super(key: key); /// Creates a widget that displays the PDF document obtained from [File]. @@ -217,27 +258,157 @@ class SfPdfViewer extends StatefulWidget { /// } /// } /// ``` - SfPdfViewer.file(File file, - {Key key, - this.canShowScrollHead = true, - this.controller, - this.onZoomLevelChanged, - this.canShowScrollStatus = true, - this.onPageChanged, - this.enableDoubleTapZooming = true, - this.onDocumentLoaded, - this.onDocumentLoadFailed, - this.canShowPaginationDialog = true}) - : _provider = FilePdf(file), + SfPdfViewer.file( + File file, { + Key key, + this.canShowScrollHead = true, + this.pageSpacing = 4, + this.controller, + this.onZoomLevelChanged, + this.canShowScrollStatus = true, + this.onPageChanged, + this.enableDoubleTapZooming = true, + this.enableTextSelection = true, + this.onTextSelectionChanged, + this.onDocumentLoaded, + this.onDocumentLoadFailed, + this.enableDocumentLinkAnnotation = true, + this.canShowPaginationDialog = true, + this.initialScrollOffset = Offset.zero, + this.initialZoomLevel = 1, + this.searchTextHighlightColor = const Color(0xFFE56E00), + }) : _provider = FilePdf(file), assert(canShowScrollHead != null), assert(canShowScrollStatus != null), + assert(initialZoomLevel != null), + assert(initialScrollOffset != null), assert(enableDoubleTapZooming != null), assert(canShowPaginationDialog != null), + assert(pageSpacing != null && pageSpacing >= 0), + assert(enableDocumentLinkAnnotation != null), + assert(searchTextHighlightColor != null), super(key: key); /// PDF file provider. final PdfProvider _provider; + /// Represents the initial zoom level to be applied when the [SfPdfViewer] widget is loaded. + /// + /// Defaults to 1.0 + /// + /// This example demonstrates how to set the initial zoom level to the [SfPdfViewer]. + /// + /// ```dart + /// class MyAppState extends State{ + /// + /// 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, + /// initialZoomLevel: 2.0, + /// ), + /// ), + /// ); + /// } + ///} + /// ``` + final double initialZoomLevel; + + /// Represents the initial scroll offset position to be displayed when the [SfPdfViewer] widget is loaded. + /// + /// Defaults to Offset(0, 0) + /// + /// This example demonstrates how to set the initial scroll offset position to the [SfPdfViewer]. + /// + /// ```dart + /// class MyAppState extends State{ + /// + /// 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, + /// initialScrollOffset: Offset(100.0, 10.0), + /// ), + /// ), + /// ); + /// } + ///} + /// ``` + final Offset initialScrollOffset; + + /// Indicates whether the document link annotation navigation can be performed or not. + /// + /// If this property is set as `false`, then the document link annotation will not be navigated. + /// + /// Defaults to `true`. + final bool enableDocumentLinkAnnotation; + + /// Represents the spacing (in pixels) between the PDF pages. + /// + /// If this property is set as `0.0`, then the spacing between the PDF pages will be removed. + /// + /// Defaults to 4.0 + /// + /// This example demonstrates how to set the page spacing in the [SfPdfViewer]. + /// + /// ```dart + /// class MyAppState extends State{ + /// + /// 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, + /// pageSpacing: 0.0, + /// ), + /// ), + /// ); + /// } + ///} + /// ``` + final double pageSpacing; + /// An object that is used to control the navigation and zooming operations /// in the [SfPdfViewer]. /// @@ -331,6 +502,26 @@ class SfPdfViewer extends StatefulWidget { /// Defaults to `true`. final bool enableDoubleTapZooming; + /// Indicates whether the text selection can be performed or not. + /// + /// The text selection can be performed by long pressing any text present in the document. + /// If this property is set as `false`, then the text selection will not happen. + /// + /// Defaults to `true`. + /// + /// _Note:_ The images in the document will not be selected and also, the multiple page + /// text selection is not supported for now. + final bool enableTextSelection; + + /// The text search highlight color to be displayed on the instances found. + /// + /// To differentiate, the current instance highlight color opacity (w.r.t this property) will be + /// higher than the other instances. The current instance highlight color opacity will be 60% + /// and the other instances color opacity will be 30%. + /// + /// Defaults to Color(0xFFE56E00) + final Color searchTextHighlightColor; + /// Called after the document is loaded in [SfPdfViewer]. /// /// The [document] in the [PdfDocumentLoadedDetails] will have the loaded PdfDocument @@ -367,6 +558,65 @@ class SfPdfViewer extends StatefulWidget { /// See also: [PdfZoomDetails]. final PdfZoomLevelChangedCallback onZoomLevelChanged; + /// Called when the text is selected or deselected in [SfPdfViewer]. + /// + /// The [globalSelectedRegion] and [selectedText] values in the + /// [PdfTextSelectionChangedDetails] will be updated when the text + /// is selected or deselected. + /// + /// See also: [PdfTextSelectionChangedDetails]. + /// + /// This example demonstrates how to show the context menu after the text selection using the [onTextSelectionChanged] callback. + /// + /// ```dart + /// class _MyHomePageState extends State { + /// OverlayEntry _overlayEntry; + /// final GlobalKey _pdfViewerKey = GlobalKey(); + /// final PdfViewerController _pdfViewerController = PdfViewerController(); + /// void _showContextMenu(BuildContext context,PdfTextSelectionChangedDetails details) { + /// final OverlayState _overlayState = Overlay.of(context); + /// _overlayEntry = OverlayEntry( + /// builder: (context) => Positioned( + /// top: details.globalSelectedRegion.top - 55, + /// left: details.globalSelectedRegion.bottomLeft.dx, + /// child: + /// FlatButton(child: Text('Copy',style: TextStyle(fontSize: 17),),onPressed: (){ + /// Clipboard.setData(ClipboardData(text: details.selectedText)); + /// _pdfViewerController.clearSelection(); + /// },color: Colors.white,), + /// ), + /// ); + /// _overlayState.insert(_overlayEntry); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text(widget.title), + /// ), + /// body: SfPdfViewer.asset( + /// 'assets/sample.pdf', + /// enableTextSelection: true, + /// onTextSelectionChanged: + /// (PdfTextSelectionChangedDetails details) { + /// if (details.selectedText == null && _overlayEntry != null) { + /// _overlayEntry.remove(); + /// _overlayEntry = null; + /// } else if (details.selectedText != null && + /// _overlayEntry == null) { + /// _showContextMenu(context, details); + /// } + /// }, + /// key: _pdfViewerKey, + /// controller: _pdfViewerController, + /// ), + /// ); + /// } + /// } + /// ``` + final PdfTextSelectionChangedCallback onTextSelectionChanged; + /// Called when the page changes in [SfPdfViewer]. /// /// Called in the following scenarios where the page changes @@ -394,11 +644,16 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { PdfViewerPlugin _plugin; ScrollController _scrollController; PdfViewerController _pdfViewerController; + CancelableOperation _getPdfFileCancellableOperation, + _pdfDocumentLoadCancellableOperation, + _getHeightCancellableOperation, + _getWidthCancellableOperation; double _scrollHeadOffset; List _originalHeight; List _originalWidth; bool _isScrollHeadDragged; bool _isScrolled; + bool _isScrollControllerInitiated = false; Orientation _deviceOrientation; double _viewportWidth; double _offsetBeforeOrientationChange; @@ -406,12 +661,17 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { int _previousPageNumber; PdfDocument _document; bool _hasError; + bool _panEnabled; + bool _isTextSelectionCleared; bool _isLoadCallbackInvoked; double _actualViewportHeight; double _actualViewportMaxScroll; final Map _pdfPages = {}; final GlobalKey _bookmarkKey = GlobalKey(); final GlobalKey _pdfContainerKey = GlobalKey(); + final Map> _pdfPagesKey = {}; + List _textCollection; + PdfTextExtractor _pdfTextExtractor; /// PdfViewer theme data. SfPdfViewerThemeData _pdfViewerThemeData; @@ -426,18 +686,31 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { @override void initState() { super.initState(); - _scrollController = ScrollController(); + _pdfViewerController = widget.controller ?? PdfViewerController(); + _pdfViewerController.addListener(_handleControllerValueChange); + if (widget.key is PageStorageKey) { + final double offset = PageStorage.of(context).readState(context); + _scrollController = ScrollController(initialScrollOffset: offset ?? 0); + final double zoomLevel = PageStorage.of(context)?.readState(context, + identifier: 'zoomLevel_' + widget.key.toString()); + if (zoomLevel != null) { + _pdfViewerController.zoomLevel = zoomLevel; + } + } else { + _scrollController = + ScrollController(initialScrollOffset: widget.initialScrollOffset.dy); + } _scrollHeadOffset = 0.0; _actualViewportHeight = _actualViewportMaxScroll = 0.0; _isScrolled = true; _offsetBeforeOrientationChange = 0; _isScrollHeadDragged = true; _hasError = false; + _panEnabled = true; + _isTextSelectionCleared = false; _loadPdfDocument(false); _previousPageNumber = 1; _isLoadCallbackInvoked = false; - _pdfViewerController = widget.controller ?? PdfViewerController(); - _pdfViewerController.addListener(_handleControllerValueChange); WidgetsBinding.instance.addObserver(this); } @@ -473,6 +746,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } if (oldWidget._provider.getUserPath() != widget._provider.getUserPath()) { + _pdfViewerController.clearSelection(); // PDF document gets loaded only when the user changes // the input source of PDF document. _loadPdfDocument(true); @@ -481,6 +755,10 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { @override void dispose() { + _getPdfFileCancellableOperation?.cancel(); + _pdfDocumentLoadCancellableOperation?.cancel(); + _getHeightCancellableOperation?.cancel(); + _getWidthCancellableOperation?.cancel(); _pdfViewerThemeData = null; _scrollController.dispose(); imageCache.clear(); @@ -488,8 +766,16 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _disposeCollection(_originalHeight); _disposeCollection(_originalWidth); _pdfPages?.clear(); + _pdfPagesKey?.clear(); _document?.dispose(); _document = null; + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.dispose(); + if (widget.onTextSelectionChanged != null) { + widget.onTextSelectionChanged(PdfTextSelectionChangedDetails(null, null)); + } _pdfViewerController.removeListener(_handleControllerValueChange); WidgetsBinding.instance.removeObserver(this); super.dispose(); @@ -520,30 +806,44 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { imageCache.clear(); _hasError = false; _isLoadCallbackInvoked = false; + _pdfPagesKey?.clear(); } /// Loads a PDF document and gets the page count from Plugin void _loadPdfDocument(bool isPdfChanged) async { try { - final String pdfPath = await (widget._provider.getPdfPath(context)); + _getPdfFileCancellableOperation = CancelableOperation.fromFuture( + widget._provider.getPdfPath(context), + ); + final String pdfPath = await _getPdfFileCancellableOperation.value; if (isPdfChanged) { _reset(); } _plugin = PdfViewerPlugin(pdfPath); - await _getPdfFile(pdfPath); + _pdfDocumentLoadCancellableOperation = + CancelableOperation.fromFuture(_getPdfFile(pdfPath)); + _document = await _pdfDocumentLoadCancellableOperation.value; + if (_document != null) { + _pdfTextExtractor = PdfTextExtractor(_document); + } + if (_document != null && widget.onDocumentLoaded != null) { + widget.onDocumentLoaded(PdfDocumentLoadedDetails(_document)); + } final int pageCount = await _plugin.initializePdfRenderer(); _pdfViewerController._pageCount = pageCount; if (pageCount > 0) { _pdfViewerController._pageNumber = 1; } - _pdfViewerController.zoomLevel = _pdfViewerController.zoomLevel ?? 1; - _getPagesHeight(); - _getPagesWidth(); + _pdfViewerController.zoomLevel ??= widget.initialZoomLevel; + _getHeightCancellableOperation = + CancelableOperation.fromFuture(_plugin.getPagesHeight()); + _originalHeight = await _getHeightCancellableOperation.value; + _getWidthCancellableOperation = + CancelableOperation.fromFuture(_plugin.getPagesWidth()); + _originalWidth = await _getWidthCancellableOperation.value; } catch (e) { _pdfViewerController._reset(); - setState(() { - _hasError = true; - }); + _hasError = true; final String errorMessage = e.toString(); if (errorMessage.contains('Invalid cross reference table') || errorMessage.contains('FormatException: Invalid radix-10 number') || @@ -577,18 +877,46 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { 'Error', 'There was an error opening this document.')); } } + } finally { + _checkMount(); } } /// Get the file of the Pdf. - Future _getPdfFile(String value) async { + Future _getPdfFile(String value) async { if (value != null) { final File pdfFile = File(value); final bytes = await pdfFile.readAsBytes(); if (bytes != null) { - setState(() { - _document = PdfDocument(inputBytes: bytes); - }); + return PdfDocument(inputBytes: bytes); + } + } + return null; + } + + /// Notify the scroll Listener after [ScrollController] attached. + void _isScrollPositionAttached() { + if (_scrollController.hasClients && !_isScrollControllerInitiated) { + _scrollControllerInitiated(); + _isScrollControllerInitiated = true; + _isLoadCallbackInvoked = true; + } + } + + /// Invoke the [PdfViewerController] methods on document load time. + void _scrollControllerInitiated() { + if (!_isScrollControllerInitiated) { + if (_pdfViewerController._horizontalOffset != null || + _pdfViewerController._verticalOffset != null) { + _pdfViewerController.jumpTo( + xOffset: _pdfViewerController._horizontalOffset, + yOffset: _pdfViewerController._verticalOffset); + } + if (_scrollController.hasClients && + _pdfViewerController._pageNavigator != null && + _pdfViewerController._pageNavigator.option != null) { + _pdfViewerController.notifyPropertyChangedListeners( + property: 'pageNavigate'); } } } @@ -631,6 +959,10 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ), ], ); + + // call PdfViewerController methods after ScrollController attached. + _isScrollPositionAttached(); + final isPdfLoaded = (_pdfViewerController.pageCount > 0 && _originalWidth != null && _originalHeight != null); @@ -658,11 +990,14 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { Center( child: PdfContainer( key: _pdfContainerKey, + panEnabled: _panEnabled, onZoomLevelChanged: widget.onZoomLevelChanged, pdfController: _pdfViewerController, scrollController: _scrollController, enableDoubleTapZooming: widget.enableDoubleTapZooming, + initialScrollOffset: widget.initialScrollOffset, + initialZoomLevel: widget.initialZoomLevel, itemBuilder: (BuildContext context, int index) { if (index == 0) { totalHeight = 0; @@ -675,25 +1010,54 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _originalWidth[index], _originalHeight[index], _viewportConstraints.maxWidth); + if (!_pdfPagesKey.containsKey(pageIndex)) { + _pdfPagesKey[pageIndex] = GlobalKey(); + } final PdfPageView page = PdfPageView( + key: _pdfPagesKey[pageIndex], imageStream: _pdfImages[pageIndex], width: calculatedSize.width, height: calculatedSize.height, + pageSpacing: widget.pageSpacing, + pdfDocument: _document, + pdfPages: _pdfPages, + scrollController: _scrollController, + pdfViewerController: _pdfViewerController, + enableDocumentLinkAnnotation: + widget.enableDocumentLinkAnnotation, + pageIndex: index, + onTextSelectionChanged: + widget.onTextSelectionChanged, + onTextSelectionDragStarted: + _handleTextSelectionDragStarted, + onTextSelectionDragEnded: + _handleTextSelectionDragEnded, + enableTextSelection: + widget.enableTextSelection, + textCollection: _textCollection, + searchTextHighlightColor: + widget.searchTextHighlightColor, + pdfTextSearchResult: + _pdfViewerController._pdfTextSearchResult, ); _pdfPages[pageIndex] = PdfPageInfo(totalHeight, calculatedSize); - totalHeight += calculatedSize.height; + totalHeight += + calculatedSize.height + widget.pageSpacing; _updateOffsetOnOrientationChange( _offsetBeforeOrientationChange, pageIndex, totalHeight); - if (widget.onDocumentLoaded != null && - pageIndex == - _pdfViewerController.pageCount && - !_isLoadCallbackInvoked) { - _isLoadCallbackInvoked = true; - widget.onDocumentLoaded( - PdfDocumentLoadedDetails(_document)); + if (_pdfPagesKey[ + _pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox != + null && + !_isTextSelectionCleared) { + _isTextSelectionCleared = true; + Future.delayed(Duration.zero, () async { + _clearSelection(); + }); } return page; }, @@ -702,7 +1066,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { Visibility( visible: widget.canShowScrollHead && _pdfViewerController.pageCount > 1, - child: ScrollHead( + child: ScrollHeadOverlay( canShowPaginationDialog: widget.canShowPaginationDialog, scrollHeadOffset: _scrollHeadOffset, @@ -740,25 +1104,12 @@ 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() { + if (widget.onTextSelectionChanged != null) { + widget.onTextSelectionChanged(PdfTextSelectionChangedDetails(null, null)); + } _bookmarkKey.currentState?.open(); } - /// Get the height of the PDF pages. - void _getPagesHeight() async { - final List originalHeight = await _plugin.getPagesHeight(); - setState(() { - _originalHeight = originalHeight; - }); - } - - /// Get the width of the PDF pages. - void _getPagesWidth() async { - final List originalWidth = await _plugin.getPagesWidth(); - setState(() { - _originalWidth = originalWidth; - }); - } - /// Get the rendered pages from plugin. Future> _getImages() { final int startPage = _pdfViewerController.pageNumber; @@ -786,13 +1137,20 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (_isScrolled) { renderedPages = _plugin?.getSpecificPages(startPage, endPage)?.then((value) { - setState(() {}); return value; }); + renderedPages.whenComplete(_checkMount); } return renderedPages; } + // Checks whether the current Widget is mounted and then relayout the Widget. + void _checkMount() { + if (super.mounted) { + setState(() {}); + } + } + /// Triggers the page changed callback when current page number is changed void _pageChanged() { if (widget.onPageChanged != null) { @@ -808,6 +1166,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _previousPageNumber = _pdfViewerController.pageNumber; } } + _checkMount(); } /// Whenever orientation is changed, PDF page is changed based on viewport @@ -826,7 +1185,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (_viewportWidth != null) { final targetOffset = initialOffset * totalHeight; WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - _pdfViewerController.jumpTo(yOffset: targetOffset); + _scrollController.jumpTo(targetOffset); _updateScrollHeadPosition( _scrollController.position.viewportDimension); }); @@ -858,18 +1217,24 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (_isScrollHeadDragged) { _previousPageNumber = _pdfViewerController.pageNumber; } + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.scrollStarted(); _isScrolled = false; } else if (notification is ScrollUpdateNotification) { _updateScrollHeadPosition(_scrollController.position.viewportDimension); - _isScrolled = false; + _isScrolled = _isLoadCallbackInvoked ? false : true; } else if (notification is ScrollEndNotification) { + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.scrollEnded(); _updateScrollHeadPosition(_scrollController.position.viewportDimension); - setState(() { - if (_isScrollHeadDragged) { - _isScrolled = true; - _pageChanged(); - } - }); + if (_isScrollHeadDragged) { + _isScrolled = true; + _pageChanged(); + } } } @@ -880,28 +1245,34 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (_scrollController.offset > 0) { final positionRatio = (_scrollController.position.pixels / (maxScrollExtent ?? _scrollController.position.maxScrollExtent)); - setState(() { - // Calculating the scroll head position based on ratio of - // current position with ListView's MaxScrollExtent - _scrollHeadOffset = - (positionRatio * (height - kPdfScrollHeadHeight)); - // As the returned values are in double. - // The ceil of the value will be applied - // ex. 1.2 means 2 will be current page number - _pdfViewerController._pageNumber = - _getPageNumber(_scrollController.offset); - }); + // Calculating the scroll head position based on ratio of + // current position with ListView's MaxScrollExtent + _scrollHeadOffset = (positionRatio * (height - kPdfScrollHeadHeight)); + // As the returned values are in double. + // The ceil of the value will be applied + // ex. 1.2 means 2 will be current page number + _pdfViewerController._pageNumber = + _getPageNumber(_scrollController.offset); } else { // This conditions gets hit when scrolled to 0.0 offset - setState(() { - _scrollHeadOffset = 0.0; - _pdfViewerController._pageNumber = 1; - }); + _scrollHeadOffset = 0.0; + _pdfViewerController._pageNumber = 1; } + _checkMount(); } } } + /// Triggers when text selection dragging started. + void _handleTextSelectionDragStarted() { + _panEnabled = false; + } + + /// Triggers when text selection dragging ended. + void _handleTextSelectionDragEnded() { + _panEnabled = true; + } + /// updates UI when scroll head drag is started. void _handleScrollHeadDragStart(DragStartDetails details) { _isScrollHeadDragged = false; @@ -915,38 +1286,34 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { // Based on the dragOffset the pdf pages must be scrolled // and scroll position must be updated - setState(() { - // This if clause condition can be split into three behaviors. - // 1. Normal case - Here, based on scroll head position ratio with - // viewport height, scroll view position is changed. - // 2. End to End cases - - // There few case, where 0.0000123(at start) and 0.99999934(at end) - // factors are computed. For these case, scroll to their ends - // in scrollview. Similarly, for out of bound drag offsets. - if (dragOffset < scrollHeadPosition && - dragOffset >= 0 && - _scrollHeadOffset != null) { - _scrollController.jumpTo(_scrollController.position.maxScrollExtent * - (dragOffset / scrollHeadPosition)); - _scrollHeadOffset = dragOffset; + // This if clause condition can be split into three behaviors. + // 1. Normal case - Here, based on scroll head position ratio with + // viewport height, scroll view position is changed. + // 2. End to End cases - + // There few case, where 0.0000123(at start) and 0.99999934(at end) + // factors are computed. For these case, scroll to their ends + // in scrollview. Similarly, for out of bound drag offsets. + if (dragOffset < scrollHeadPosition && + dragOffset >= 0 && + _scrollHeadOffset != null) { + _scrollController.jumpTo(_scrollController.position.maxScrollExtent * + (dragOffset / scrollHeadPosition)); + _scrollHeadOffset = dragOffset; + } else { + if (dragOffset < 0) { + _scrollController.jumpTo(_scrollController.position.minScrollExtent); + _scrollHeadOffset = 0.0; } else { - if (dragOffset < 0) { - _scrollController.jumpTo(_scrollController.position.minScrollExtent); - _scrollHeadOffset = 0.0; - } else { - _scrollController.jumpTo(_scrollController.position.maxScrollExtent); - _scrollHeadOffset = scrollHeadPosition; - } + _scrollController.jumpTo(_scrollController.position.maxScrollExtent); + _scrollHeadOffset = scrollHeadPosition; } - }); + } } /// updates UI when scroll head drag is ended. void _handleScrollHeadDragEnd(DragEndDetails details) { - setState(() { - _isScrollHeadDragged = true; - _isScrolled = true; - }); + _isScrollHeadDragged = true; + _isScrolled = true; _pageChanged(); } @@ -957,6 +1324,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// Jump to the bookmark location. void _jumpToBookmark(PdfBookmark bookmark) { + _clearSelection(); double heightPercentage; double bookmarkOffset; PdfPage pdfPage; @@ -987,12 +1355,28 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _scrollController.jumpTo(offset); } + /// clears the text selection. + bool _clearSelection() { + return _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.clearSelection(); + } + /// Call the method according to property name. void _handleControllerValueChange({String property}) { if (property == 'jumpToBookmark') { _jumpToBookmark(_pdfViewerController._pdfBookmark); + } else if (property == 'zoomLevel') { + PageStorage.of(context).writeState( + context, _pdfViewerController.zoomLevel, + identifier: 'zoomLevel_' + widget.key.toString()); + } else if (property == 'clearTextSelection') { + _pdfViewerController._clearTextSelection = _clearSelection(); } else if (property == 'jumpTo') { + _clearSelection(); if (_scrollController != null && + _scrollController.hasClients && _pdfViewerController._verticalOffset != null) { final yOffset = _pdfViewerController._verticalOffset; if (yOffset < _scrollController.position.minScrollExtent) { @@ -1008,7 +1392,10 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ?.jumpHorizontally(_pdfViewerController._horizontalOffset); } } else if (property == 'pageNavigate') { - if (_pdfViewerController._pageNavigator != null) { + _clearSelection(); + if (_pdfViewerController._pageNavigator != null && + _scrollController != null && + _scrollController.hasClients) { switch (_pdfViewerController._pageNavigator.option) { case Navigation.jumpToPage: if (_pdfViewerController._pageNavigator.index > 0 && @@ -1039,6 +1426,73 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { break; } } + } else if (property == 'searchText') { + if (_pdfViewerController._searchText != null) { + _pdfViewerController._pdfTextSearchResult + .removeListener(_handleTextSearch); + + _textCollection = _pdfTextExtractor.findText( + [_pdfViewerController._searchText], + searchOption: + _pdfViewerController._textSearchOption.toString().isNotEmpty || + _pdfViewerController._textSearchOption != null + ? _pdfViewerController._textSearchOption + : TextSearchOption.both, + ); + + if (_textCollection.isEmpty) { + _pdfViewerController._pdfTextSearchResult?._currentOccurrenceIndex = + 0; + + _pdfViewerController._pdfTextSearchResult?._totalSearchTextCount = 0; + _pdfViewerController._pdfTextSearchResult?._updateResult(false); + } else { + if (_pdfViewerController.pageNumber == 1) { + if (_pdfViewerController.pageNumber - 1 != + _textCollection[0].pageIndex) { + _pdfViewerController.jumpTo( + yOffset: + _pdfPages[_textCollection[0].pageIndex + 1].pageOffset); + } + _pdfViewerController._pdfTextSearchResult?._currentOccurrenceIndex = + 1; + } else { + for (int i = 0; i < _textCollection.length; i++) { + if (_textCollection[i].pageIndex == + _pdfViewerController.pageNumber - 1) { + _pdfViewerController.jumpTo( + yOffset: + _pdfPages[_textCollection[i].pageIndex + 1].pageOffset); + _pdfViewerController + ._pdfTextSearchResult?._currentOccurrenceIndex = i + 1; + break; + } else { + if (_textCollection[i].pageIndex > + _pdfViewerController.pageNumber - 1) { + if (_textCollection[i].pageIndex + 1 >= + _pdfViewerController.pageCount) { + _pdfViewerController + .jumpToPage(_pdfViewerController.pageCount); + } else { + _pdfViewerController.jumpTo( + yOffset: _pdfPages[_textCollection[i].pageIndex + 1] + .pageOffset); + } + _pdfViewerController + ._pdfTextSearchResult?._currentOccurrenceIndex = i + 1; + break; + } + } + } + } + + _pdfViewerController._pdfTextSearchResult?._totalSearchTextCount = + _textCollection.length; + _pdfViewerController._pdfTextSearchResult?._updateResult(true); + } + _pdfViewerController._pdfTextSearchResult + .addListener(_handleTextSearch); + } } } @@ -1060,6 +1514,173 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } return pageNumber; } + + /// Call the method according to property name. + void _handleTextSearch({String property}) { + if (property == 'nextInstance') { + int _pageNumber; + bool matched = true; + if (_textCollection[_pdfViewerController + ._pdfTextSearchResult.currentInstanceIndex - + 1] + .pageIndex == + _pdfViewerController.pageNumber - 1) { + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = + _pdfViewerController._pdfTextSearchResult.currentInstanceIndex + 1; + } else { + for (int i = 0; i < _textCollection.length; i++) { + if (_textCollection[i].pageIndex == + _pdfViewerController.pageNumber - 1) { + if (_textCollection[i].pageIndex != _pageNumber) { + _pdfViewerController + ._pdfTextSearchResult._currentOccurrenceIndex = i + 1; + _pageNumber = _textCollection[i].pageIndex; + matched = false; + } + } else if (_textCollection[i].pageIndex > + _pdfViewerController.pageNumber - 1) { + if (matched) { + _pdfViewerController + ._pdfTextSearchResult._currentOccurrenceIndex = i + 1; + } + break; + } + } + } + + if (_pdfViewerController._pdfTextSearchResult.currentInstanceIndex - 1 == + _pdfViewerController._pdfTextSearchResult.totalInstanceCount) { + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = 1; + } + final int currentInstancePageIndex = (_textCollection[_pdfViewerController + ._pdfTextSearchResult.currentInstanceIndex - + 1] + .pageIndex + + 1); + + final double topOffset = _textCollection[ + _pdfViewerController._pdfTextSearchResult.currentInstanceIndex - + 1] + .bounds + .top; + + final double heightPercentage = + _originalHeight[currentInstancePageIndex - 1] / + _pdfPages[currentInstancePageIndex].pageSize.height; + + if (_deviceOrientation == Orientation.landscape) { + if (_pdfPages[currentInstancePageIndex].pageOffset + + (topOffset / heightPercentage) <= + _scrollController.offset || + _pdfPages[currentInstancePageIndex].pageOffset + + (topOffset / heightPercentage) >= + _scrollController.offset + + _scrollController.position.viewportDimension) { + _pdfViewerController.jumpTo( + yOffset: (_pdfPages[currentInstancePageIndex].pageOffset + + (topOffset / heightPercentage))); + } + } else { + if (currentInstancePageIndex != _pdfViewerController.pageNumber) { + _pdfViewerController.jumpToPage(currentInstancePageIndex); + } + if (_pdfPages[currentInstancePageIndex].pageOffset + + (topOffset / heightPercentage) <= + _scrollController.offset || + _pdfPages[currentInstancePageIndex].pageOffset + + (topOffset / heightPercentage) >= + _scrollController.offset + + _scrollController.position.viewportDimension) { + _pdfViewerController.jumpTo( + yOffset: (_pdfPages[currentInstancePageIndex].pageOffset + + (topOffset / heightPercentage))); + } + } + } else if (property == 'previousInstance') { + _clearSelection(); + bool matched = true; + if (_textCollection[_pdfViewerController + ._pdfTextSearchResult.currentInstanceIndex - + 1] + .pageIndex == + _pdfViewerController.pageNumber - 1) { + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = + _pdfViewerController._pdfTextSearchResult.currentInstanceIndex - 1; + } else { + for (int i = 0; i < _textCollection.length; i++) { + if (_textCollection[i].pageIndex == + _pdfViewerController.pageNumber - 1) { + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = + i + 1; + matched = false; + } else if (_textCollection[i].pageIndex > + _pdfViewerController.pageNumber - 1) { + if (matched) { + _pdfViewerController._pdfTextSearchResult + ._currentOccurrenceIndex = i - 1 == 0 ? 1 : i; + } + break; + } + } + } + + if (_pdfViewerController._pdfTextSearchResult.currentInstanceIndex - 1 < + 0) { + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = + _pdfViewerController._pdfTextSearchResult.totalInstanceCount; + } + + final int currentInstancePageIndex = (_textCollection[_pdfViewerController + ._pdfTextSearchResult.currentInstanceIndex - + 1] + .pageIndex + + 1); + + final double topOffset = _textCollection[ + _pdfViewerController._pdfTextSearchResult.currentInstanceIndex - + 1] + .bounds + .top; + + final double heightPercentage = + _originalHeight[currentInstancePageIndex - 1] / + _pdfPages[currentInstancePageIndex].pageSize.height; + + if (_deviceOrientation == Orientation.landscape) { + if (_pdfPages[currentInstancePageIndex].pageOffset + + (topOffset / heightPercentage) <= + _scrollController.offset || + _pdfPages[currentInstancePageIndex].pageOffset + + (topOffset / heightPercentage) >= + _scrollController.offset + + _scrollController.position.viewportDimension) { + _pdfViewerController.jumpTo( + yOffset: (_pdfPages[currentInstancePageIndex].pageOffset + + (topOffset / heightPercentage))); + } + } else { + if (currentInstancePageIndex != _pdfViewerController.pageNumber) { + _pdfViewerController.jumpToPage(currentInstancePageIndex); + } + if (_pdfPages[currentInstancePageIndex].pageOffset + + (topOffset / heightPercentage) <= + _scrollController.offset || + _pdfPages[currentInstancePageIndex].pageOffset + + (topOffset / heightPercentage) >= + _scrollController.offset + + _scrollController.position.viewportDimension) { + _pdfViewerController.jumpTo( + yOffset: (_pdfPages[currentInstancePageIndex].pageOffset + + (topOffset / heightPercentage))); + } + } + } else if (property == 'clear') { + _textCollection = null; + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = 0; + _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = 0; + _pdfViewerController._pdfTextSearchResult._updateResult(false); + } + } } /// An object that is used to control the navigation and zooming operations @@ -1121,7 +1742,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// ``` class PdfViewerController extends _ValueChangeNotifier { /// Zoom level - double _zoomLevel = 1.0; + double _zoomLevel; /// Current page number int _currentPageNumber = 0; @@ -1129,6 +1750,12 @@ class PdfViewerController extends _ValueChangeNotifier { /// Total number of pages in Pdf. int _totalPages = 0; + /// Searched text value + String _searchText; + + /// option for text search + TextSearchOption _textSearchOption; + /// Sets the current page number. set _pageNumber(int num) { _currentPageNumber = num; @@ -1144,6 +1771,9 @@ class PdfViewerController extends _ValueChangeNotifier { /// PdfBookmark instance PdfBookmark _pdfBookmark; + /// PdfTextSearchResult instance + final PdfTextSearchResult _pdfTextSearchResult = PdfTextSearchResult(); + /// Vertical Offset double _verticalOffset; @@ -1153,6 +1783,9 @@ class PdfViewerController extends _ValueChangeNotifier { /// Represents different page navigation option Pagination _pageNavigator; + /// Returns `true`, if the text selection is cleared properly. + bool _clearTextSelection = false; + /// Zoom level of a document in the [SfPdfViewer]. /// /// Zoom level value can be set between 1.0 to 3.0. The maximum allowed zoom @@ -1785,6 +2418,148 @@ class PdfViewerController extends _ValueChangeNotifier { 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. + /// + /// * text - _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 { + /// + /// PdfViewerController _pdfViewerController; + /// PdfTextSearchResult _searchResult; + /// + /// @override + /// void initState() { + /// _pdfViewerController = PdfViewerController(); + /// super.initState(); + /// } + /// + /// 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( + /// child: Text('YES'), + /// onPressed: () { + /// _searchResult?.nextInstance(); + /// Navigator.of(context).pop(); + /// }, + /// ), + /// FlatButton( + /// child: Text('NO'), + /// onPressed: () { + /// _searchResult?.clear(); + /// Navigator.of(context).pop(); + /// }, + /// ), + /// ], + /// ); + /// }, + /// ); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// title: Text('Syncfusion Flutter PdfViewer'), + /// actions: [ + /// IconButton( + /// icon: Icon( + /// Icons.search, + /// color: Colors.white, + /// ), + /// onPressed: () async { + /// _searchResult = await _pdfViewerController?.searchText( + /// 'PDF', 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: () { + /// if (_searchResult?.currentInstanceIndex == + /// _searchResult?.totalInstanceCount) { + /// _showDialog(context); + /// } + /// else { + /// _searchResult?.nextInstance(); + /// } + /// }, + /// ), + /// ), + /// Visibility( + /// visible: _searchResult?.hasResult ?? false, + /// child: IconButton( + /// icon: Icon( + /// Icons.keyboard_arrow_down, + /// color: Colors.white, + /// ), + /// onPressed: () { + /// _searchResult?.previousInstance(); + /// }, + /// ), + /// ), + /// ], + /// ), + /// body: + /// SfPdfViewer.asset( + /// 'assets/flutter-succinctly.pdf', + /// controller: _pdfViewerController, + /// searchTextHighlightColor: Colors.blue + /// ) + /// ); + /// } + ///} + ///''' + Future searchText(String searchText, + {TextSearchOption searchOption}) async { + _searchText = searchText; + _textSearchOption = searchOption; + notifyPropertyChangedListeners(property: 'searchText'); + return _pdfTextSearchResult; + } + + /// Clears the text selection in [SfPdfViewer]. + /// + /// Returns `true`, if the text selection is cleared properly. + bool clearSelection() { + notifyPropertyChangedListeners(property: 'clearTextSelection'); + return _clearTextSelection; + } + /// Resets the controller value when widget is updated. void _reset() { _zoomLevel = 1.0; @@ -1794,6 +2569,78 @@ class PdfViewerController extends _ValueChangeNotifier { } } +/// PdfTextSearchResult holds the details of TextSearch +class PdfTextSearchResult extends _ValueChangeNotifier { + /// Current instance number of the searched text. + int _currentInstanceIndex = 0; + + /// Total search text instances found in the PDF document. + int _totalInstanceCount = 0; + + /// Indicates whether the text search context is alive for searching + bool _hasResult = false; + + /// Sets the current highlighted search text index in the document. + set _currentOccurrenceIndex(int num) { + _currentInstanceIndex = num; + notifyPropertyChangedListeners(property: 'currentInstance'); + } + + /// The current highlighted search text index in the document. + int get currentInstanceIndex { + return _currentInstanceIndex; + } + + /// Sets the total instance of the searched text in the PDF document. + set _totalSearchTextCount(int totalInstanceCount) { + _totalInstanceCount = totalInstanceCount; + notifyPropertyChangedListeners(property: 'totalInstance'); + } + + /// Indicates the total instance of the searched text in the PDF document. + int get totalInstanceCount { + return _totalInstanceCount; + } + + /// Updates whether the text search context is alive for searching + void _updateResult(bool hasResult) { + _hasResult = hasResult; + notifyPropertyChangedListeners(property: 'result'); + } + + /// Indicates whether the text search context is alive for searching + bool get hasResult { + return _hasResult; + } + + /// 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'); + } + + /// Moves to the previous searched text instance in the document. + /// + /// Using this method, the [SfPdfViewer] will move to the previous searched text + /// 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'); + } + + /// Clears the [PdfTextSearchResult] object and cancels the search process. + /// + /// Once this method is called, the search process will be cancelled in the UI and + /// the [PdfTextSearchResult] object will be cleared, which in turn changes the + /// [hasResult] property value to 'false`. + void clear() { + notifyPropertyChangedListeners(property: 'clear'); + } +} + /// _ValueChangeNotifier class listener invoked whenever PdfViewerController property changed. class _ValueChangeNotifier { _PdfControllerListener listener; diff --git a/packages/syncfusion_flutter_pdfviewer/pubspec.yaml b/packages/syncfusion_flutter_pdfviewer/pubspec.yaml index 5cb801f2b..03c34ac0e 100644 --- a/packages/syncfusion_flutter_pdfviewer/pubspec.yaml +++ b/packages/syncfusion_flutter_pdfviewer/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: flutter: sdk: flutter vector_math: ">=1.1.0 <3.0.0" + async: ^2.4.2 path_provider: ^1.6.18 syncfusion_flutter_core: path: ../syncfusion_flutter_core diff --git a/packages/syncfusion_flutter_signaturepad/CHANGELOG.md b/packages/syncfusion_flutter_signaturepad/CHANGELOG.md index 83e7a85e0..6b20acd46 100644 --- a/packages/syncfusion_flutter_signaturepad/CHANGELOG.md +++ b/packages/syncfusion_flutter_signaturepad/CHANGELOG.md @@ -1,4 +1,4 @@ -## [18.3.35-beta] - 10/01/2020 +## [18.3.35-beta] Initial release. diff --git a/packages/syncfusion_flutter_signaturepad/README.md b/packages/syncfusion_flutter_signaturepad/README.md index 7a51363e6..a7d262218 100644 --- a/packages/syncfusion_flutter_signaturepad/README.md +++ b/packages/syncfusion_flutter_signaturepad/README.md @@ -16,13 +16,13 @@ This library is used to capture a signature through drawing gestures. You can us ## Table of contents -- [SignaturePad features](#signature_pad_features) -- [Get the demo application](#get_the_demo) -- [Useful links](#useful_links) +- [SignaturePad features](#signaturepad-features) +- [Get the demo application](#get-the-demo-application) +- [Useful links](#other-useful-links) - [Installation](#installation) -- [SignaturePad Getting Started](#slider_getting_started) -- [Support and feedback](#support_and_feedback) -- [About Syncfusion](#about_syncfusion) +- [SignaturePad Getting Started](#getting-started) +- [Support and feedback](#support-and-feedback) +- [About Syncfusion](#about-syncfusion) ## SignaturePad features @@ -53,7 +53,7 @@ Take a look at the following to learn more about the Syncfusion Flutter Signatur ## Installation -Install the latest version from [pub.dev](https://pub.dev/packages/syncfusion_flutter_sliders#-installing-tab-). +Install the latest version from [pub.dev](https://pub.dev/packages/syncfusion_flutter_signaturepad/install). ## Getting started @@ -63,7 +63,7 @@ Import the following package. import 'package:syncfusion_flutter_signaturepad/signaturepad.dart'; ``` -### **Add SignaturePad to widget tree ** +### Add SignaturePad to widget tree Add the SignaturePad widget as a child of any widget. Here, the SignaturePad widget is added as a child of the Container widget. @@ -80,7 +80,7 @@ Add the SignaturePad widget as a child of any widget. Here, the SignaturePad wid ``` -### **Add SignaturePad elements ** +### Add SignaturePad elements Update elements such as stroke color, minimum stroke width, maximum stroke width, and background color to capture a realistic signature. In the following code example, the SignaturePad is added inside a Container widget to get a size for it. @@ -220,7 +220,7 @@ You can clear the signature drawn in the SignaturePad using the clear() method a } ``` -## Support and feedback +## 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). * 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. diff --git a/packages/syncfusion_flutter_sliders/README.md b/packages/syncfusion_flutter_sliders/README.md index 37ad87929..c09e4c2e1 100644 --- a/packages/syncfusion_flutter_sliders/README.md +++ b/packages/syncfusion_flutter_sliders/README.md @@ -138,7 +138,7 @@ Widget build(BuildContext context) { interval: 20, showTicks: true, showLabels: true, - showTooltip: true, + enableTooltip: true, minorTicksPerInterval: 1, onChanged: (dynamic value){ setState(() { @@ -199,7 +199,7 @@ Widget build(BuildContext context) { interval: 20, showTicks: true, showLabels: true, - showTooltip: true, + enableTooltip: true, minorTicksPerInterval: 1, onChanged: (SfRangeValues values){ setState(() { diff --git a/packages/syncfusion_flutter_sliders/example/android/.gitignore b/packages/syncfusion_flutter_sliders/example/android/.gitignore new file mode 100644 index 000000000..bc2100d8f --- /dev/null +++ b/packages/syncfusion_flutter_sliders/example/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/packages/syncfusion_flutter_sliders/example/ios/.gitignore b/packages/syncfusion_flutter_sliders/example/ios/.gitignore new file mode 100644 index 000000000..e96ef602b --- /dev/null +++ b/packages/syncfusion_flutter_sliders/example/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/syncfusion_flutter_sliders/example/lib/main.dart b/packages/syncfusion_flutter_sliders/example/lib/main.dart index d3fa39eaf..76cddb46f 100644 --- a/packages/syncfusion_flutter_sliders/example/lib/main.dart +++ b/packages/syncfusion_flutter_sliders/example/lib/main.dart @@ -1,5 +1,5 @@ import 'package:syncfusion_flutter_sliders/sliders.dart'; -import 'package:syncfusion_flutter_charts/charts.dart' hide LabelPlacement; +// import 'package:syncfusion_flutter_charts/charts.dart' hide LabelPlacement; import 'package:intl/intl.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -31,16 +31,16 @@ class MyHomePage extends StatefulWidget { 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), - ]; + // 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), + // ]; final DateTime _dateMin = DateTime(2003, 01, 01); final DateTime _dateMax = DateTime(2010, 01, 01); @@ -72,24 +72,25 @@ class _MyHomePageState extends State { dateFormat: DateFormat.y(), showTicks: true, showLabels: true, - child: Container( - child: SfCartesianChart( - margin: const EdgeInsets.all(0), - primaryXAxis: DateTimeAxis( - minimum: _dateMin, - maximum: _dateMax, - isVisible: false, - ), - primaryYAxis: NumericAxis(isVisible: false, maximum: 4), - series: >[ - SplineAreaSeries( - dataSource: _chartData, - xValueMapper: (Data sales, _) => sales.x, - yValueMapper: (Data sales, _) => sales.y) - ], - ), - height: 200, - ), + // child: Container( + // child: SfCartesianChart( + // margin: const EdgeInsets.all(0), + // primaryXAxis: DateTimeAxis( + // minimum: dateMin, + // maximum: dateMax, + // isVisible: false, + // ), + // primaryYAxis: + // NumericAxis(isVisible: false, maximum: 4), + // series: >[ + // SplineAreaSeries( + // dataSource: chartData, + // xValueMapper: (Data sales, _) => sales.x, + // yValueMapper: (Data sales, _) => sales.y) + // ], + // ), + // height: 200, + // ), ), ), ), diff --git a/packages/syncfusion_flutter_sliders/lib/sliders.dart b/packages/syncfusion_flutter_sliders/lib/sliders.dart index 691755fc8..d5c833aa9 100644 --- a/packages/syncfusion_flutter_sliders/lib/sliders.dart +++ b/packages/syncfusion_flutter_sliders/lib/sliders.dart @@ -1,22 +1,20 @@ -library sliders; +/// Syncfusion Flutter Sliders library is written natively in Dart for creating +/// highly interactive and UI-rich slider controls for filtering purposes. +/// +/// To use, import `package:syncfusion_flutter_sliders/sliders.dart`; +/// +/// See also: +/// * [Syncfusion Flutter Slider product page](https://www.syncfusion.com/flutter-widgets/flutter-slider) +/// * [Syncfusion Flutter Range Slider product page](https://www.syncfusion.com/flutter-widgets/flutter-range-slider) +/// * [Syncfusion Flutter Range Selector product page](https://www.syncfusion.com/flutter-widgets/flutter-range-selector) +/// * [User guide documentation for Slider](https://help.syncfusion.com/flutter/slider/overview) +/// * [User guide documentation for Range Slider](https://help.syncfusion.com/flutter/range-slider/overview) +/// * [User guide documentation for Range Selector](https://help.syncfusion.com/flutter/range-selector/overview) -import 'dart:async'; -import 'dart:math' as math; -import 'dart:ui'; -import 'package:intl/intl.dart' show DateFormat, NumberFormat; -import 'package:flutter/material.dart'; -import 'package:flutter/gestures.dart' - show - GestureArenaTeam, - TapGestureRecognizer, - HorizontalDragGestureRecognizer; -import 'package:flutter/rendering.dart'; -import 'package:syncfusion_flutter_core/core.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; +library sliders; -part 'src/common.dart'; -part 'src/slider.dart'; -part 'src/range_slider.dart'; -part 'src/range_selector.dart'; -part 'src/slider_shapes.dart'; -part 'src/render_slider_base.dart'; +export 'src/common.dart'; +export 'src/range_selector.dart'; +export 'src/range_slider.dart'; +export 'src/slider.dart'; +export 'src/slider_shapes.dart' hide SfMinorTickShape; diff --git a/packages/syncfusion_flutter_sliders/lib/src/common.dart b/packages/syncfusion_flutter_sliders/lib/src/common.dart index d9e7cb04b..c70fd9a17 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/common.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/common.dart @@ -1,4 +1,5 @@ -part of sliders; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; /// Signature for formatting or changing the whole numeric or date label text. typedef LabelFormatterCallback = String Function( @@ -22,7 +23,7 @@ typedef TooltipTextFormatterCallback = String Function( /// 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]. +/// The value will be either [double] or [DateTime] based on the `values`. typedef SfSliderSemanticFormatterCallback = String Function(dynamic value); typedef SfRangeSliderSemanticFormatterCallback = String Function( @@ -45,53 +46,53 @@ enum LabelPlacement { enum DateIntervalType { /// Date interval is year. /// - /// For example, if [min] is DateTime(2000, 01, 01, 00) and - /// [max] is DateTime(2005, 12, 31, 24) and [interval] is 1 and - /// [dateIntervalType] is [years] then range slider + /// For example, if `min` is DateTime(2000, 01, 01, 00) and + /// `max` is DateTime(2005, 12, 31, 24) and `interval` is 1 and + /// `dateIntervalType` is [years] then range slider /// will render labels for 2000, 2001, 2002, 2003, 2004, 2005 respectively. years, /// Date interval is month. /// - /// For example, if [min] is DateTime(2000, 01, 01, 00) and - /// [max] is DateTime(2000, 12, 31, 24) and [interval] is 3 and - /// [dateIntervalType] is [months] then range slider will render labels + /// For example, if `min` is DateTime(2000, 01, 01, 00) and + /// `max` is DateTime(2000, 12, 31, 24) and `interval` is 3 and + /// `dateIntervalType` is [months] then range slider will render labels /// for [Jan 01, 2000], [Apr 01, 2000], [Jul 01, 2000], [Oct 01, 2000] /// and [Jan 01, 2001] respectively. months, /// Date interval is day. /// - /// For example, if [min] is DateTime(2000, 01, 01, 00) and - /// [max] is DateTime(2000, 01, 25, 24) and [interval] is 5 and - /// [dateIntervalType] is [days] then range slider will render labels + /// For example, if `min` is DateTime(2000, 01, 01, 00) and + /// `max` is DateTime(2000, 01, 25, 24) and `interval` is 5 and + /// `dateIntervalType` is [days] then range slider will render labels /// for [Jan 01, 2000], [Jan 06, 2000], [Jan 11, 2000], [Jan 16, 2000], /// [Jan 21, 2001] and [Jan 26, 2001] respectively. days, /// Date interval is hour. /// - /// For example, if [min] is DateTime(2000, 01, 01, 09) and - /// [max] is DateTime(2000, 01, 01, 17) and [interval] is 4 and - /// [dateIntervalType] is [hours] then range slider will render labels for + /// For example, if `min` is DateTime(2000, 01, 01, 09) and + /// `max` is DateTime(2000, 01, 01, 17) and `interval` is 4 and + /// `dateIntervalType` is [hours] then range slider will render labels for /// [Jan 01, 2000 09:00], [Jan 01, 2000 13:00], and [Jan 01, 2000 17:00] /// respectively. hours, /// Date interval is minute. /// - /// For example, if [min] is DateTime(2000, 01, 01, 09) and - /// [max] is DateTime(2000, 01, 01, 10) and [interval] is 15 and - /// [dateIntervalType] is [minutes] then range slider will render labels for + /// For example, if `min` is DateTime(2000, 01, 01, 09) and + /// `max` is DateTime(2000, 01, 01, 10) and `interval` is 15 and + /// `dateIntervalType` is [minutes] then range slider will render labels for /// [Jan 01, 2000 09:00], [Jan 01, 2000 09:15], [Jan 01, 2000 09:30], /// [Jan 01, 2000 09:45]and [Jan 01, 2000 10:00] respectively. minutes, /// Date interval is second. /// - /// For example, if [min] is DateTime(2000, 01, 01, 09, 00) and - /// [max] is DateTime(2000, 01, 01, 09, 01) and [interval] is 20 and - /// [dateIntervalType] is [seconds] then range slider will render labels for + /// For example, if `min` is DateTime(2000, 01, 01, 09, 00) and + /// `max` is DateTime(2000, 01, 01, 09, 01) and `interval` is 20 and + /// `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 @@ -106,18 +107,7 @@ enum SfThumb { end } -/// Represents the [SfRangeSlider] or [SfRangeSelector] child elements. -enum _ChildElements { - /// Represents the icon for [SfRangeValues.start] thumb. - startThumbIcon, - - /// Represents the icon for [SfRangeValues.start] thumb. - endThumbIcon, - - /// Represents the content of [SfRangeSelector]. - child, -} - +/// Represents the dragging behavior of the [SfRangeSelector] thumbs. enum SliderDragMode { /// When [SliderDragMode] is set to [SliderDragMode.onThumb], /// individual thumb can be moved by dragging it. @@ -136,20 +126,10 @@ enum SliderDragMode { both } -enum _PointerType { - down, - - move, - - up -} - -const double _minPreferredTouchWidth = 20; - /// Represents the current selected values of [SfRangeSlider] /// and [SfRangeSelector]. @immutable -class SfRangeValues { +class SfRangeValues extends DiagnosticableTree { /// Represents the current selected values of [SfRangeSlider] /// and [SfRangeSelector]. const SfRangeValues(this.start, this.end); @@ -160,7 +140,7 @@ class SfRangeValues { /// Represents the [SfRangeValues.end] thumb. final dynamic end; - SfRangeValues _copyWith({dynamic start, dynamic end}) { + SfRangeValues copyWith({dynamic start, dynamic end}) { // HACK: In web, 0.00 and 0 are considered as identical. // So, we had considered both double and int. if (start != null && @@ -179,14 +159,21 @@ class SfRangeValues { return SfRangeValues(start ?? this.start, end ?? this.end); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('start', start)); + properties.add(DiagnosticsProperty('end', end)); + } } /// The class which is used to set step duration for date discrete support. /// /// See also: /// -/// * [interval], for setting the interval. -class SliderStepDuration { +/// * `interval`, for setting the interval. +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. @@ -198,35 +185,68 @@ class SliderStepDuration { this.minutes = 0, this.seconds = 0}); + /// Moves the thumbs based on years. + /// + /// For example, if `min` is DateTime(2000, 01, 01) and + /// `max` is DateTime(2006, 01, 01) and `SliderDuration(years: 2)` then the + /// thumb will get moved at DateTime(2000, 01, 01), DateTime(2002, 07, 01), + /// DateTime(2004, 01, 01), and DateTime(2006, 07, 01). final int years; + + /// Moves the thumbs based on months. + /// + /// For example, if `min` is DateTime(2000, 01, 01) and + /// `max` is DateTime(2000, 10, 01) and `SliderDuration(months: 3)` then the + /// thumb will get moved at DateTime(2000, 01, 01), DateTime(2000, 04, 01), + /// DateTime(2000, 07, 01), and DateTime(2000, 10, 01). final int months; + + /// Moves the thumbs based on days. + /// + /// For example, if `min` is DateTime(2000, 01, 01) and + /// `max` is DateTime(2000, 01, 20) and `SliderDuration(days: 5)` then the + /// thumb will get moved at DateTime(2000, 01, 01), DateTime(2000, 01, 06), + /// DateTime(2000, 01, 11), DateTime(2000, 01, 16), and + /// DateTime(2000, 01, 20). final int days; + + /// Moves the thumbs based on hours. + /// + /// For example, if `min` is DateTime(2000, 01, 01, 05) and + /// `max` is DateTime(2006, 01, 01, 20) and `SliderDuration(hours: 5)` then + /// the thumb will get moved at DateTime(2000, 01, 01, 05), + /// DateTime(2000, 01, 01, 11), DateTime(2000, 01, 01, 16), + /// and DateTime(2000, 01, 01, 20). final int hours; + + /// Moves the thumbs based on minutes. + /// + /// For example, if `min` is DateTime(2000, 01, 01, 05, 10) and + /// `max` is DateTime(2006, 01, 01, 05, 40) and `SliderDuration(minutes: 10)` + /// then the thumb will get moved at DateTime(2000, 01, 01, 05, 10), + /// DateTime(2000, 01, 01, 05, 20), DateTime(2000, 01, 01, 05, 30), + /// and DateTime(2000, 01, 01, 05, 40). final int minutes; + + /// Moves the thumbs based on seconds. + /// + /// For example, if `min` is DateTime(2000, 01, 01, 05, 10, 00) and + /// `max` is DateTime(2006, 01, 01, 05, 12, 00) and + /// `SliderDuration(seconds: 30)` then the thumb will get moved at + /// DateTime(2006, 01, 01, 05, 10, 00), DateTime(2006, 01, 01, 05, 10, 30)), + /// DateTime(2006, 01, 01, 05, 11, 00), DateTime(2006, 01, 01, 05, 11, 30), + /// and DateTime(2006, 01, 01, 05, 12, 00). final int seconds; -} -// Minimum tooltip radius -const double _minPaddleTopCircleRadius = 16; -// Difference between paddle top circle and neck radius -const double _neckDifference = 3.0; -// minimum bottom neck radius -const double _minBottomNeckRadius = 4.0; -// Thumb radius is greater than default thumb radius, -// increasing the bottom neck radius based on thumb radius. -const double _defaultThumbRadius = 10.0; -// To get the shape of the paddle, -// move the neck as quarters of paddle circle radius. -const double _moveNeckValue = 0.25; -const double _textPadding = 8.0; - -const Offset _tooltipTextPadding = Offset(15, 15); -const double _tooltipTriangleHeight = 7; -const double _tooltipTriangleWidth = 12; -const double _minTooltipWidth = 47.0; -const double _minTooltipHeight = 37.0; -const double _cornerRadius = 4.0; - -const double _defaultElevation = 1.0; -const double _tappedElevation = 6.0; -const Color _shadowColor = Colors.black; + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + + properties.add(IntProperty('years', years)); + properties.add(IntProperty('months', months)); + properties.add(IntProperty('days', days)); + properties.add(IntProperty('hours', hours)); + properties.add(IntProperty('minutes', minutes)); + properties.add(IntProperty('seconds', seconds)); + } +} diff --git a/packages/syncfusion_flutter_sliders/lib/src/constants.dart b/packages/syncfusion_flutter_sliders/lib/src/constants.dart new file mode 100644 index 000000000..374e5defe --- /dev/null +++ b/packages/syncfusion_flutter_sliders/lib/src/constants.dart @@ -0,0 +1,43 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +const double minPaddleTopCircleRadius = 16; +// Difference between paddle top circle and neck radius +const double neckDifference = 3.0; +// minimum bottom neck radius +const double minBottomNeckRadius = 4.0; +// Thumb radius is greater than default thumb radius, +// increasing the bottom neck radius based on thumb radius. +const double defaultThumbRadius = 10.0; +// To get the shape of the paddle, +// move the neck as quarters of paddle circle radius. +const double moveNeckValue = 0.25; +const double textPadding = 8.0; + +const Offset tooltipTextPadding = Offset(15, 15); +const double tooltipTriangleHeight = 7; +const double tooltipTriangleWidth = 12; +const double minTooltipWidth = 47.0; +const double minTooltipHeight = 37.0; +const double cornerRadius = 4.0; + +const double defaultElevation = 1.0; +const double tappedElevation = 6.0; +const Color shadowColor = Colors.black; + +const double minPreferredTouchWidth = 20; + +enum PointerType { down, move, up } + +/// Represents the [SfRangeSlider] or [SfRangeSelector] child elements. +enum ChildElements { + /// Represents the icon for [SfRangeValues.start] thumb. + startThumbIcon, + + /// Represents the icon for [SfRangeValues.start] thumb. + endThumbIcon, + + /// Represents the content of [SfRangeSelector]. + child, +} diff --git a/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart b/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart index fccb94ae5..46c27bca7 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart @@ -1,4 +1,23 @@ -part of sliders; +import 'dart:async'; +import 'dart:math' as math; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart' + show + GestureArenaTeam, + TapGestureRecognizer, + HorizontalDragGestureRecognizer; +import 'package:flutter/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'; +import 'constants.dart'; +import 'render_slider_base.dart'; +import 'slider_shapes.dart'; /// A Material Design range selector. /// @@ -98,6 +117,7 @@ part of sliders; /// * [RangeController](https://pub.dev/documentation/syncfusion_flutter_core/latest/core/RangeController-class.html), for coordinating between [SfRangeSelector] and the widget which listens to it. /// * [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, @@ -114,7 +134,7 @@ class SfRangeSelector extends StatefulWidget { this.showTicks = false, this.showLabels = false, this.showDivisors = false, - this.showTooltip = false, + this.enableTooltip = false, this.enableIntervalSelection = false, this.enableDeferredUpdate = false, this.dragMode = SliderDragMode.onThumb, @@ -222,17 +242,17 @@ class SfRangeSelector extends StatefulWidget { /// /// Built-in support for selection and zooming with [SfChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/charts-library.html). /// - /// Range controller contains [start] and [end] values. + /// Range controller contains `start` and `end` values. /// - /// [start] - represents the currently selected start value + /// `start` - represents the currently selected start value /// of the range selector. /// The start thumb of the range selector was drawn /// corresponding to this value. /// - /// [end] - represents the currently selected end value of the range selector. + /// `end` - represents the currently selected end value of the range selector. /// The end thumb of the range selector was drawn corresponding to this value. /// - /// [start] and [end] can be either `double` or `DateTime`. + /// `start` and `end` can be either `double` or `DateTime`. /// /// ## Selection in [SfChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/charts-library.html). /// @@ -492,7 +512,7 @@ class SfRangeSelector extends StatefulWidget { /// Option to select discrete values. /// /// [stepSize] doesn’t work for [DateTime] range selector and - /// [lockRange] doesn't works with [stepSize]. + /// `lockRange` doesn't works with [stepSize]. /// /// For example, if [min] is 0.0 and [max] is 10.0 and [stepSize] is 2.0, /// the range selector will move the thumbs at 0.0, 2.0, 4.0 and so on. @@ -535,7 +555,7 @@ class SfRangeSelector extends StatefulWidget { /// max: DateTime(2020, 01, 01), /// initialValues: SfRangeValues( /// DateTime(2017, 04,01), DateTime(2018, 08, 01)), - /// showTooltip: true, + /// enableTooltip: true, /// stepDuration: SliderDuration(years: 1, months: 6), /// interval: 2, /// showLabels: true, @@ -725,7 +745,7 @@ class SfRangeSelector extends StatefulWidget { /// By default, tooltip text is formatted with either [numberFormat] or /// [dateFormat]. /// - /// This snippet shows how to show tooltip in [SfRangeSelector]. + /// This snippet shows how to enable tooltip in [SfRangeSelector]. /// /// ```dart /// SfRangeValues _initialValues = SfRangeValues(4.0, 8.0); @@ -737,7 +757,7 @@ class SfRangeSelector extends StatefulWidget { /// interval: 1, /// showTicks: true, /// showLabels: true, - /// showTooltip: true, + /// enableTooltip: true, /// child: Container( /// height: 200, /// color: Colors.green[100], @@ -749,7 +769,7 @@ class SfRangeSelector extends StatefulWidget { /// /// * [tooltipTextFormatterCallback], for changing the default tooltip text. /// * [SfRangeSelectorThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfRangeSelectorThemeData-class.html), for customizing the appearance of the tooltip text. - final bool showTooltip; + final bool enableTooltip; /// Option to select the particular interval based on /// the position of the tap or click. @@ -772,7 +792,7 @@ class SfRangeSelector extends StatefulWidget { /// initialValues: _initialValues, /// interval: 20, /// showLabels: true, - /// showTooltip: true, + /// enableTooltip: true, /// enableIntervalSelection: true, /// showTicks: true, /// child: Container( @@ -1095,7 +1115,7 @@ class SfRangeSelector extends StatefulWidget { /// initialValues: _initialValues, /// interval: 4, /// showLabels: true, - /// showTooltip: true, + /// enableTooltip: true, /// dateFormat: DateFormat('h a'), /// dateIntervalType: DateIntervalType.hours, /// tooltipTextFormatterCallback: @@ -1170,7 +1190,7 @@ class SfRangeSelector extends StatefulWidget { /// showLabels: true, /// showTicks: true, /// interval: 20, - /// showTooltip: true, + /// enableTooltip: true, /// tooltipShape: SfPaddleTooltipShape(), /// child: Container( /// height: 150, @@ -1182,8 +1202,9 @@ class SfRangeSelector extends StatefulWidget { /// The content of range selector. /// - /// If it is not null, [SfRangeSelectorThemeData]'s [activeRegionColor] - /// and [inactiveRegionColor] applied on the child. + /// If it is not null, [SfRangeSelectorThemeData]'s + /// [SfRangeSelectorThemeData.activeRegionColor] and + /// [SfRangeSelectorThemeData.inactiveRegionColor] applied on the child. /// /// The active side of the range selector is between the left and right thumb. /// The inactive side of the range selector is between the @@ -1200,7 +1221,8 @@ class SfRangeSelector extends StatefulWidget { /// Defaults to `null`. /// /// It is possible to set any widget inside the left thumb. If the widget - /// exceeds the size of the thumb, increase the [thumbRadius] based on it. + /// exceeds the size of the thumb, increase the + /// [SfSliderThemeData.thumbRadius] based on it. /// /// This snippet shows how to show start thumb icon in [SfRangeSelector]. /// @@ -1214,7 +1236,7 @@ class SfRangeSelector extends StatefulWidget { /// interval: 1, /// showTicks: true, /// showLabels: true, - /// showTooltip: true, + /// enableTooltip: true, /// child: Container( /// height: 200, /// color: Colors.green[100], @@ -1237,7 +1259,8 @@ class SfRangeSelector extends StatefulWidget { /// Defaults to `null`. /// /// It is possible to set any widget inside the right thumb. If the widget - /// exceeds the size of the thumb, increase the [thumbRadius] based on it. + /// exceeds the size of the thumb, increase the + /// [SfSliderThemeData.thumbRadius] based on it. /// /// This snippet shows how to show end thumb icon in [SfRangeSelector]. /// @@ -1251,7 +1274,7 @@ class SfRangeSelector extends StatefulWidget { /// interval: 1, /// showTicks: true, /// showLabels: true, - /// showTooltip: true, + /// enableTooltip: true, /// child: Container( /// height: 200, /// color: Colors.green[100], @@ -1271,6 +1294,87 @@ class SfRangeSelector extends StatefulWidget { @override _SfRangeSelectorState createState() => _SfRangeSelectorState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + 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(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) { + properties.add(stepDuration.toDiagnosticsNode(name: 'stepDuration')); + } + + properties.add(IntProperty('minorTicksPerInterval', minorTicksPerInterval)); + properties.add(FlagProperty('showTicks', + value: showTicks, + ifTrue: 'Ticks are showing', + ifFalse: 'Ticks are not showing', + showName: false)); + properties.add(FlagProperty('showLabels', + value: showLabels, + ifTrue: 'Labels are showing', + ifFalse: 'Labels are not showing', + showName: false)); + properties.add(FlagProperty('showDivisors', + value: showDivisors, + ifTrue: 'Divisors are showing', + ifFalse: 'Divisors are not showing', + showName: false)); + properties.add(FlagProperty('enableTooltip', + value: enableTooltip, + ifTrue: 'Tooltip is enabled', + ifFalse: 'Tooltip is disabled', + showName: false)); + properties.add(FlagProperty('enableIntervalSelection', + value: enableIntervalSelection, + ifTrue: 'Interval selection is enabled', + ifFalse: 'Interval selection is disabled', + showName: false)); + 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)); + if (initialValues.start.runtimeType == DateTime) { + properties.add(StringProperty( + 'dateFormat', + 'Formatted value is ' + + (dateFormat.format(initialValues.start)).toString())); + } + + properties.add(ObjectFlagProperty>.has( + 'onChanged', onChanged)); + properties.add( + EnumProperty('dateIntervalType', dateIntervalType)); + properties.add(ObjectFlagProperty.has( + 'tooltipTextFormatterCallback', tooltipTextFormatterCallback)); + properties.add(ObjectFlagProperty.has( + 'labelFormatterCallback', labelFormatterCallback)); + properties.add(ObjectFlagProperty>.has( + 'semanticFormatterCallback', semanticFormatterCallback)); + } } class _SfRangeSelectorState extends State @@ -1456,7 +1560,7 @@ class _SfRangeSelectorState extends State showTicks: widget.showTicks, showLabels: widget.showLabels, showDivisors: widget.showDivisors, - showTooltip: widget.showTooltip, + enableTooltip: widget.enableTooltip, enableIntervalSelection: widget.enableIntervalSelection, deferUpdate: widget.enableDeferredUpdate, dragMode: widget.dragMode, @@ -1472,12 +1576,12 @@ class _SfRangeSelectorState extends State tooltipTextFormatterCallback: widget.tooltipTextFormatterCallback ?? _getFormattedTooltipText, semanticFormatterCallback: widget.semanticFormatterCallback, - trackShape: widget.trackShape ?? _SfTrackShape(), - divisorShape: widget.divisorShape ?? _SfDivisorShape(), - overlayShape: widget.overlayShape ?? _SfOverlayShape(), - thumbShape: widget.thumbShape ?? _SfThumbShape(), - tickShape: widget.tickShape ?? _SfTickShape(), - minorTickShape: widget.minorTickShape ?? _SfMinorTickShape(), + trackShape: widget.trackShape ?? SfTrackShape(), + divisorShape: widget.divisorShape ?? SfDivisorShape(), + overlayShape: widget.overlayShape ?? SfOverlayShape(), + thumbShape: widget.thumbShape ?? SfThumbShape(), + tickShape: widget.tickShape ?? SfTickShape(), + minorTickShape: widget.minorTickShape ?? SfMinorTickShape(), tooltipShape: widget.tooltipShape ?? SfRectangularTooltipShape(), child: widget.child, rangeSelectorThemeData: _getRangeSelectorThemeData(themeData), @@ -1503,7 +1607,7 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { this.showTicks, this.showLabels, this.showDivisors, - this.showTooltip, + this.enableTooltip, this.enableIntervalSelection, this.deferUpdate, this.dragMode, @@ -1543,7 +1647,7 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { final bool showTicks; final bool showLabels; final bool showDivisors; - final bool showTooltip; + final bool enableTooltip; final bool enableIntervalSelection; final bool deferUpdate; final SliderDragMode dragMode; @@ -1590,7 +1694,7 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { showTicks: showTicks, showLabels: showLabels, showDivisors: showDivisors, - showTooltip: showTooltip, + enableTooltip: enableTooltip, enableIntervalSelection: enableIntervalSelection, deferUpdate: deferUpdate, dragMode: dragMode, @@ -1631,7 +1735,7 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { ..showTicks = showTicks ..showLabels = showLabels ..showDivisors = showDivisors - ..showTooltip = showTooltip + ..enableTooltip = enableTooltip ..enableIntervalSelection = enableIntervalSelection ..deferUpdate = deferUpdate ..dragMode = dragMode @@ -1661,8 +1765,8 @@ class _RenderRangeSelectorElement extends RenderObjectElement { _RenderRangeSelectorElement(_RangeSelectorRenderObjectWidget rangeSelector) : super(rangeSelector); - final Map<_ChildElements, Element> _slotToChild = <_ChildElements, Element>{}; - final Map _childToSlot = {}; + final Map _slotToChild = {}; + final Map _childToSlot = {}; @override _RangeSelectorRenderObjectWidget get widget => super.widget; @@ -1670,7 +1774,7 @@ class _RenderRangeSelectorElement extends RenderObjectElement { @override _RenderRangeSelector get renderObject => super.renderObject; - void _mountChild(Widget newWidget, _ChildElements slot) { + void _mountChild(Widget newWidget, ChildElements slot) { final Element oldChild = _slotToChild[slot]; final Element newChild = updateChild(oldChild, newWidget, slot); if (oldChild != null) { @@ -1683,7 +1787,7 @@ class _RenderRangeSelectorElement extends RenderObjectElement { } } - void _updateChild(Widget widget, _ChildElements slot) { + void _updateChild(Widget widget, ChildElements slot) { final Element oldChild = _slotToChild[slot]; final Element newChild = updateChild(oldChild, widget, slot); if (oldChild != null) { @@ -1696,15 +1800,15 @@ class _RenderRangeSelectorElement extends RenderObjectElement { } } - void _updateRenderObject(RenderObject child, _ChildElements slot) { + void _updateRenderObject(RenderObject child, ChildElements slot) { switch (slot) { - case _ChildElements.startThumbIcon: + case ChildElements.startThumbIcon: renderObject.startThumbIcon = child; break; - case _ChildElements.endThumbIcon: + case ChildElements.endThumbIcon: renderObject.endThumbIcon = child; break; - case _ChildElements.child: + case ChildElements.child: renderObject.child = child; break; } @@ -1718,32 +1822,32 @@ class _RenderRangeSelectorElement extends RenderObjectElement { @override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); - _mountChild(widget.startThumbIcon, _ChildElements.startThumbIcon); - _mountChild(widget.endThumbIcon, _ChildElements.endThumbIcon); - _mountChild(widget.child, _ChildElements.child); + _mountChild(widget.startThumbIcon, ChildElements.startThumbIcon); + _mountChild(widget.endThumbIcon, ChildElements.endThumbIcon); + _mountChild(widget.child, ChildElements.child); } @override void update(_RangeSelectorRenderObjectWidget newWidget) { super.update(newWidget); assert(widget == newWidget); - _updateChild(widget.startThumbIcon, _ChildElements.startThumbIcon); - _updateChild(widget.endThumbIcon, _ChildElements.endThumbIcon); - _updateChild(widget.child, _ChildElements.child); + _updateChild(widget.startThumbIcon, ChildElements.startThumbIcon); + _updateChild(widget.endThumbIcon, ChildElements.endThumbIcon); + _updateChild(widget.child, ChildElements.child); } @override - void insertChildRenderObject(RenderObject child, dynamic slotValue) { + void insertRenderObjectChild(RenderObject child, dynamic slotValue) { assert(child is RenderBox); - assert(slotValue is _ChildElements); - final _ChildElements slot = slotValue; + assert(slotValue is ChildElements); + final ChildElements slot = slotValue; _updateRenderObject(child, slot); assert(renderObject.childToSlot.keys.contains(child)); assert(renderObject.slotToChild.keys.contains(slot)); } @override - void removeChildRenderObject(RenderObject child) { + void removeRenderObjectChild(RenderObject child, dynamic slot) { assert(child is RenderBox); assert(renderObject.childToSlot.keys.contains(child)); _updateRenderObject(null, renderObject.childToSlot[child]); @@ -1752,12 +1856,13 @@ class _RenderRangeSelectorElement extends RenderObjectElement { } @override - void moveChildRenderObject(RenderObject child, dynamic slotValue) { + void moveRenderObjectChild( + RenderObject child, dynamic oldSlot, dynamic newSlot) { assert(false, 'not reachable'); } } -class _RenderRangeSelector extends _RenderBaseSlider { +class _RenderRangeSelector extends RenderBaseSlider { _RenderRangeSelector({ dynamic min, dynamic max, @@ -1771,7 +1876,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { bool showTicks, bool showLabels, bool showDivisors, - bool showTooltip, + bool enableTooltip, bool enableIntervalSelection, bool deferUpdate, SliderDragMode dragMode, @@ -1806,7 +1911,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { showTicks: showTicks, showLabels: showLabels, showDivisors: showDivisors, - showTooltip: showTooltip, + enableTooltip: enableTooltip, labelPlacement: labelPlacement, numberFormat: numberFormat, dateFormat: dateFormat, @@ -1872,7 +1977,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { _values.end.millisecondsSinceEpoch.toDouble()); } unformattedLabels = []; - _updateTextPainter(); + updateTextPainter(); if (_enableIntervalSelection) { _state.startPositionController.value = @@ -1886,11 +1991,11 @@ class _RenderRangeSelector extends _RenderBaseSlider { final _SfRangeSelectorState _state; - final Map<_ChildElements, RenderBox> slotToChild = - <_ChildElements, RenderBox>{}; + final Map slotToChild = + {}; - final Map childToSlot = - {}; + final Map childToSlot = + {}; Animation _overlayStartAnimation; @@ -2037,10 +2142,10 @@ class _RenderRangeSelector extends _RenderBaseSlider { @override set sliderThemeData(SfSliderThemeData value) { - if (_sliderThemeData == value) { + if (super.sliderThemeData == value) { return; } - _sliderThemeData = value; + super.sliderThemeData = value; maxTrackHeight = getMaxTrackHeight(); if (value is SfRangeSelectorThemeData) { @@ -2055,7 +2160,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { set startThumbIcon(RenderBox value) { _startThumbIcon = - _updateChild(_startThumbIcon, value, _ChildElements.startThumbIcon); + _updateChild(_startThumbIcon, value, ChildElements.startThumbIcon); } RenderBox get endThumbIcon => _endThumbIcon; @@ -2063,7 +2168,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { set endThumbIcon(RenderBox value) { _endThumbIcon = - _updateChild(_endThumbIcon, value, _ChildElements.endThumbIcon); + _updateChild(_endThumbIcon, value, ChildElements.endThumbIcon); } @override @@ -2072,7 +2177,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { @override set child(RenderBox value) { - _child = _updateChild(_child, value, _ChildElements.child); + _child = _updateChild(_child, value, ChildElements.child); } // The returned list is ordered for hit testing. @@ -2105,15 +2210,13 @@ class _RenderRangeSelector extends _RenderBaseSlider { // 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 _onTapDown(TapDownDetails details) { - currentPointerType = _PointerType.down; + currentPointerType = PointerType.down; _interactionStartX = globalToLocal(details.globalPosition).dx; currentX = _interactionStartX; _beginInteraction(); @@ -2132,7 +2235,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { void _onDragUpdate(DragUpdateDetails details) { isInteractionEnd = false; - currentPointerType = _PointerType.move; + currentPointerType = PointerType.move; currentX = globalToLocal(details.globalPosition).dx; _updateRangeValues(deltaX: details.delta.dx); markNeedsPaint(); @@ -2160,8 +2263,8 @@ class _RenderRangeSelector extends _RenderBaseSlider { if ((_dragMode == SliderDragMode.both || _dragMode == SliderDragMode.betweenThumbs) && - startPosition < (currentX - _minPreferredTouchWidth) && - (currentX + _minPreferredTouchWidth) < endPosition) { + startPosition < (currentX - minPreferredTouchWidth) && + (currentX + minPreferredTouchWidth) < endPosition) { if (_isDragStart) { _isLocked = true; } else { @@ -2191,7 +2294,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { } void _forwardTooltipAnimation() { - if (_showTooltip) { + if (enableTooltip) { willDrawTooltip = true; _state.tooltipAnimationController.forward(); _state.tooltipDelayTimer?.cancel(); @@ -2241,13 +2344,13 @@ class _RenderRangeSelector extends _RenderBaseSlider { final double startValue = math.min(value, end - minThumbGap); final dynamic actualStartValue = getActualValue(valueInDouble: startValue); - newValues = values._copyWith(start: actualStartValue); + newValues = values.copyWith(start: actualStartValue); break; case SfThumb.end: final double endValue = math.max(value, start + minThumbGap); final dynamic actualEndValue = getActualValue(valueInDouble: endValue); - newValues = values._copyWith(end: actualEndValue); + newValues = values.copyWith(end: actualEndValue); break; } } @@ -2302,14 +2405,14 @@ class _RenderRangeSelector extends _RenderBaseSlider { _isDragging = false; _state.overlayStartController.reverse(); _state.overlayEndController.reverse(); - if (_showTooltip && _state.tooltipDelayTimer == null) { + if (enableTooltip && _state.tooltipDelayTimer == null) { _state.tooltipAnimationController.reverse(); } isInteractionEnd = true; _isLocked = false; _isDragStart = false; - currentPointerType = _PointerType.up; + currentPointerType = PointerType.up; markNeedsPaint(); } } @@ -2402,7 +2505,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { start = isDateTime ? DateTime.fromMillisecondsSinceEpoch(currentLabel.toInt()) : currentLabel; - end = _max; + end = max; rangeValues = SfRangeValues(start, end); } } @@ -2410,7 +2513,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { } RenderBox _updateChild( - RenderBox oldChild, RenderBox newChild, _ChildElements slot) { + RenderBox oldChild, RenderBox newChild, ChildElements slot) { if (oldChild != null) { dropChild(oldChild); childToSlot.remove(oldChild); @@ -2477,28 +2580,38 @@ class _RenderRangeSelector extends _RenderBaseSlider { ..color = _inactiveRegionColor; if (child != null && child.size.height > 1 && child.size.width > 1) { final double halfActiveTrackHeight = - _sliderThemeData.activeTrackHeight / 2; + sliderThemeData.activeTrackHeight / 2; final double halfInactiveTrackHeight = - _sliderThemeData.inactiveTrackHeight / 2; - final bool isMaxActive = _sliderThemeData.activeTrackHeight > - _sliderThemeData.inactiveTrackHeight; + sliderThemeData.inactiveTrackHeight / 2; + final bool isMaxActive = sliderThemeData.activeTrackHeight > + sliderThemeData.inactiveTrackHeight; + Offset leftThumbCenter; + Offset rightThumbCenter; + if (textDirection == TextDirection.rtl) { + leftThumbCenter = endThumbCenter; + rightThumbCenter = startThumbCenter; + } else { + leftThumbCenter = startThumbCenter; + rightThumbCenter = endThumbCenter; + } + //Below values are used to fit active and inactive region into the track. final double inactiveRegionAdj = isMaxActive ? halfActiveTrackHeight - halfInactiveTrackHeight : 0; final double activeRegionAdj = !isMaxActive ? halfInactiveTrackHeight - halfActiveTrackHeight : 0; context.canvas.drawRect( - Rect.fromLTRB(trackRect.left, offset.dy, startThumbCenter.dx, + Rect.fromLTRB(trackRect.left, offset.dy, leftThumbCenter.dx, trackRect.top + inactiveRegionAdj), paint); paint.color = _activeRegionColor; context.canvas.drawRect( - Rect.fromLTRB(startThumbCenter.dx, offset.dy, endThumbCenter.dx, + Rect.fromLTRB(leftThumbCenter.dx, offset.dy, rightThumbCenter.dx, trackRect.top + activeRegionAdj), paint); paint.color = _inactiveRegionColor; context.canvas.drawRect( - Rect.fromLTRB(endThumbCenter.dx, offset.dy, trackRect.right, + Rect.fromLTRB(rightThumbCenter.dx, offset.dy, trackRect.right, trackRect.top + inactiveRegionAdj), paint); } @@ -2518,10 +2631,10 @@ class _RenderRangeSelector extends _RenderBaseSlider { thumbShape.paint(context, thumbCenter, parentBox: this, child: thumbIcon, - themeData: _sliderThemeData, + themeData: sliderThemeData, currentValues: _values, enableAnimation: _stateAnimation, - textDirection: _textDirection, + textDirection: textDirection, thumb: isLeftThumbActive ? SfThumb.end : SfThumb.start); thumbCenter = isLeftThumbActive ? startThumbCenter : endThumbCenter; @@ -2529,7 +2642,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { // Drawing overlay. overlayShape.paint(context, thumbCenter, parentBox: this, - themeData: _sliderThemeData, + themeData: sliderThemeData, currentValues: _values, animation: isLeftThumbActive ? _overlayStartAnimation : _overlayEndAnimation, @@ -2545,10 +2658,10 @@ class _RenderRangeSelector extends _RenderBaseSlider { thumbShape.paint(context, thumbCenter, parentBox: this, child: thumbIcon, - themeData: _sliderThemeData, + themeData: sliderThemeData, currentValues: _values, enableAnimation: _stateAnimation, - textDirection: _textDirection, + textDirection: textDirection, thumb: activeThumb); } @@ -2561,7 +2674,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { Rect trackRect) { if (willDrawTooltip) { final Paint paint = Paint() - ..color = _sliderThemeData.tooltipBackgroundColor + ..color = sliderThemeData.tooltipBackgroundColor ..style = PaintingStyle.fill ..strokeWidth = 0; @@ -2571,7 +2684,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { String tooltipText = tooltipTextFormatterCallback( actualText, getFormattedText(actualText)); TextSpan textSpan = - TextSpan(text: tooltipText, style: _sliderThemeData.tooltipTextStyle); + TextSpan(text: tooltipText, style: sliderThemeData.tooltipTextStyle); textPainter.text = textSpan; textPainter.layout(); @@ -2596,7 +2709,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { tooltipShape.paint(context, thumbCenter, Offset(actualTrackOffset.dx, tooltipStartY), textPainter, parentBox: this, - sliderThemeData: _sliderThemeData, + sliderThemeData: sliderThemeData, paint: paint, animation: _tooltipAnimation, trackRect: trackRect); @@ -2607,7 +2720,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { tooltipText = tooltipTextFormatterCallback( actualText, getFormattedText(actualText)); textSpan = - TextSpan(text: tooltipText, style: _sliderThemeData.tooltipTextStyle); + TextSpan(text: tooltipText, style: sliderThemeData.tooltipTextStyle); textPainter.text = textSpan; textPainter.layout(); @@ -2636,7 +2749,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { tooltipShape.paint(context, thumbCenter, Offset(actualTrackOffset.dx, tooltipStartY), textPainter, parentBox: this, - sliderThemeData: _sliderThemeData, + sliderThemeData: sliderThemeData, paint: paint, animation: _tooltipAnimation, trackRect: trackRect); @@ -2828,7 +2941,7 @@ class _RenderRangeSelector extends _RenderBaseSlider { // Drawing track. final Rect trackRect = - trackShape.getPreferredRect(this, _sliderThemeData, actualTrackOffset); + trackShape.getPreferredRect(this, sliderThemeData, actualTrackOffset); final double thumbStartPosition = getFactorFromValue(actualValues.start) * trackRect.width; final double thumbEndPosition = @@ -2841,10 +2954,10 @@ class _RenderRangeSelector extends _RenderBaseSlider { trackShape.paint( context, actualTrackOffset, null, startThumbCenter, endThumbCenter, parentBox: this, - themeData: _sliderThemeData, + themeData: sliderThemeData, currentValues: _values, enableAnimation: _stateAnimation, - textDirection: _textDirection); + textDirection: textDirection); if (showLabels || showTicks || showDivisors) { drawLabelsTicksAndDivisors(context, trackRect, offset, null, @@ -2885,4 +2998,29 @@ class _RenderRangeSelector extends _RenderBaseSlider { } } } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(StringProperty( + 'deferredUpdateDelay', _deferUpdateDelay.toString() + ' ms')); + properties.add(StringProperty( + 'thumbSize', thumbShape.getPreferredSize(sliderThemeData).toString())); + properties.add(StringProperty( + 'activeDivisorSize', + divisorShape + .getPreferredSize(sliderThemeData, isActive: true) + .toString())); + properties.add(StringProperty( + 'inactiveDivisorSize', + divisorShape + .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())); + } } diff --git a/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart b/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart index 88a3902bf..fb35197af 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart @@ -1,4 +1,22 @@ -part of sliders; +import 'dart:async'; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/foundation.dart'; +import 'package:intl/intl.dart' show DateFormat, NumberFormat; +import 'package:flutter/material.dart'; +import 'package:flutter/gestures.dart' + show + GestureArenaTeam, + TapGestureRecognizer, + HorizontalDragGestureRecognizer; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import 'common.dart'; +import 'constants.dart'; +import 'render_slider_base.dart'; +import 'slider_shapes.dart'; /// A Material Design range slider. /// @@ -97,6 +115,7 @@ part of sliders; /// date labels. /// * [SfRangeSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfRangeSliderThemeData-class.html), for customizing the visual appearance of the range slider. class SfRangeSlider extends StatefulWidget { + /// Creates a [SfRangeSlider]. const SfRangeSlider( {Key key, this.min = 0.0, @@ -110,7 +129,7 @@ class SfRangeSlider extends StatefulWidget { this.showTicks = false, this.showLabels = false, this.showDivisors = false, - this.showTooltip = false, + this.enableTooltip = false, this.enableIntervalSelection = false, this.inactiveColor, this.activeColor, @@ -349,7 +368,7 @@ class SfRangeSlider extends StatefulWidget { /// min: DateTime(2015, 01, 01), /// max: DateTime(2020, 01, 01), /// values: _values, - /// showTooltip: true, + /// enableTooltip: true, /// stepDuration: SliderDuration(years: 1, months: 6), /// interval: 2, /// showLabels: true, @@ -518,7 +537,7 @@ class SfRangeSlider extends StatefulWidget { /// By default, tooltip text is formatted with either [numberFormat] or /// [dateFormat]. /// - /// This snippet shows how to show tooltip in [SfRangeSlider]. + /// This snippet shows how to enable tooltip in [SfRangeSlider]. /// /// ```dart /// SfRangeValues _values = SfRangeValues(4.0, 8.0); @@ -530,7 +549,7 @@ class SfRangeSlider extends StatefulWidget { /// interval: 1, /// showTicks: true, /// showLabels: true, - /// showTooltip: true, + /// enableTooltip: true, /// onChanged: (SfRangeValues newValues) { /// setState(() { /// _values = newValues; @@ -543,7 +562,7 @@ class SfRangeSlider extends StatefulWidget { /// /// * [tooltipTextFormatterCallback], for changing the default tooltip text. /// * [SfRangeSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfRangeSliderThemeData-class.html), for customizing the appearance of the tooltip text. - final bool showTooltip; + final bool enableTooltip; /// Option to select the particular interval based on /// the position of the tap or click. @@ -566,7 +585,7 @@ class SfRangeSlider extends StatefulWidget { /// values: _values, /// interval: 20, /// showLabels: true, - /// showTooltip: true, + /// enableTooltip: true, /// enableIntervalSelection: true, /// showTicks: true, /// onChanged: (SfRangeValues newValues) { @@ -826,7 +845,7 @@ class SfRangeSlider extends StatefulWidget { /// values: _values, /// interval: 4, /// showLabels: true, - /// showTooltip: true, + /// enableTooltip: true, /// dateFormat: DateFormat('h a'), /// dateIntervalType: DateIntervalType.hours, /// tooltipTextFormatterCallback: @@ -903,7 +922,7 @@ class SfRangeSlider extends StatefulWidget { /// showLabels: true, /// showTicks: true, /// interval: 20, - /// showTooltip: true, + /// enableTooltip: true, /// tooltipShape: SfPaddleTooltipShape(), /// onChanged: (SfRangeValues newValues) { /// setState(() { @@ -919,7 +938,8 @@ class SfRangeSlider extends StatefulWidget { /// Defaults to `null`. /// /// It is possible to set any widget inside the left thumb. If the widget - /// exceeds the size of the thumb, increase the [thumbRadius] based on it. + /// exceeds the size of the thumb, increase the + /// [SfSliderThemeData.thumbRadius] based on it. /// /// This snippet shows how to show start thumb icon in [SfRangeSlider]. /// @@ -933,7 +953,7 @@ class SfRangeSlider extends StatefulWidget { /// interval: 1, /// showTicks: true, /// showLabels: true, - /// showTooltip: true, + /// enableTooltip: true, /// onChanged: (SfRangeValues newValues) { /// setState(() { /// _values = newValues; @@ -958,7 +978,8 @@ class SfRangeSlider extends StatefulWidget { /// Defaults to `null`. /// /// It is possible to set any widget inside the right thumb. If the widget - /// exceeds the size of the thumb, increase the [thumbRadius] based on it. + /// exceeds the size of the thumb, increase the + /// [SfSliderThemeData.thumbRadius] based on it. /// /// This snippet shows how to show end thumb icon in [SfRangeSlider]. /// @@ -972,7 +993,7 @@ class SfRangeSlider extends StatefulWidget { /// interval: 1, /// showTicks: true, /// showLabels: true, - /// showTooltip: true, + /// enableTooltip: true, /// endThumbIcon: Icon( /// Icons.home, /// color: Colors.green, @@ -994,6 +1015,70 @@ class SfRangeSlider extends StatefulWidget { @override _SfRangeSliderState createState() => _SfRangeSliderState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add( + values.toDiagnosticsNode(name: 'values'), + ); + properties.add(DiagnosticsProperty('min', min)); + properties.add(DiagnosticsProperty('max', max)); + properties.add(ObjectFlagProperty>( + 'onChanged', onChanged, + ifNull: 'disabled')); + 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', + value: showTicks, + ifTrue: 'Ticks are showing', + ifFalse: 'Ticks are not showing', + showName: false)); + properties.add(FlagProperty('showLabels', + value: showLabels, + ifTrue: 'Labels are showing', + ifFalse: 'Labels are not showing', + showName: false)); + properties.add(FlagProperty('showDivisors', + value: showDivisors, + ifTrue: 'Divisors are showing', + ifFalse: 'Divisors are not showing', + showName: false)); + properties.add(FlagProperty('enableTooltip', + value: enableTooltip, + ifTrue: 'Tooltip is enabled', + ifFalse: 'Tooltip is disabled', + showName: false)); + 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)); + if (values.start.runtimeType == DateTime) { + properties.add(StringProperty( + 'dateFormat', + 'Formatted value is ' + + (dateFormat.format(values.start)).toString())); + } + properties.add( + EnumProperty('dateIntervalType', dateIntervalType)); + properties.add(ObjectFlagProperty.has( + 'tooltipTextFormatterCallback', tooltipTextFormatterCallback)); + properties.add(ObjectFlagProperty.has( + 'labelFormatterCallback', labelFormatterCallback)); + properties.add(ObjectFlagProperty>.has( + 'semanticFormatterCallback', semanticFormatterCallback)); + } } class _SfRangeSliderState extends State @@ -1177,7 +1262,7 @@ class _SfRangeSliderState extends State showTicks: widget.showTicks, showLabels: widget.showLabels, showDivisors: widget.showDivisors, - showTooltip: widget.showTooltip, + enableTooltip: widget.enableTooltip, enableIntervalSelection: widget.enableIntervalSelection, inactiveColor: widget.inactiveColor ?? themeData.primaryColor.withOpacity(0.24), @@ -1191,12 +1276,12 @@ class _SfRangeSliderState extends State tooltipTextFormatterCallback: widget.tooltipTextFormatterCallback ?? _getFormattedTooltipText, semanticFormatterCallback: widget.semanticFormatterCallback, - trackShape: widget.trackShape ?? _SfTrackShape(), - divisorShape: widget.divisorShape ?? _SfDivisorShape(), - overlayShape: widget.overlayShape ?? _SfOverlayShape(), - thumbShape: widget.thumbShape ?? _SfThumbShape(), - tickShape: widget.tickShape ?? _SfTickShape(), - minorTickShape: widget.minorTickShape ?? _SfMinorTickShape(), + trackShape: widget.trackShape ?? SfTrackShape(), + divisorShape: widget.divisorShape ?? SfDivisorShape(), + overlayShape: widget.overlayShape ?? SfOverlayShape(), + thumbShape: widget.thumbShape ?? SfThumbShape(), + tickShape: widget.tickShape ?? SfTickShape(), + minorTickShape: widget.minorTickShape ?? SfMinorTickShape(), tooltipShape: widget.tooltipShape ?? SfRectangularTooltipShape(), rangeSliderThemeData: _getRangeSliderThemeData(themeData, isActive), state: this, @@ -1220,7 +1305,7 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { this.showTicks, this.showLabels, this.showDivisors, - this.showTooltip, + this.enableTooltip, this.enableIntervalSelection, this.inactiveColor, this.activeColor, @@ -1256,7 +1341,7 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { final bool showTicks; final bool showLabels; final bool showDivisors; - final bool showTooltip; + final bool enableTooltip; final bool enableIntervalSelection; final Color inactiveColor; @@ -1298,7 +1383,7 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { showTicks: showTicks, showLabels: showLabels, showDivisors: showDivisors, - showTooltip: showTooltip, + enableTooltip: enableTooltip, enableIntervalSelection: enableIntervalSelection, inactiveColor: inactiveColor, activeColor: activeColor, @@ -1338,7 +1423,7 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { ..showTicks = showTicks ..showLabels = showLabels ..showDivisors = showDivisors - ..showTooltip = showTooltip + ..enableTooltip = enableTooltip ..enableIntervalSelection = enableIntervalSelection ..inactiveColor = inactiveColor ..activeColor = activeColor @@ -1366,9 +1451,9 @@ class _RenderRangeSliderElement extends RenderObjectElement { _RenderRangeSliderElement(_RangeSliderRenderObjectWidget rangeSlider) : super(rangeSlider); - final Map<_ChildElements, Element> _slotToChild = <_ChildElements, Element>{}; + final Map _slotToChild = {}; - final Map _childToSlot = {}; + final Map _childToSlot = {}; @override _RangeSliderRenderObjectWidget get widget => super.widget; @@ -1376,7 +1461,7 @@ class _RenderRangeSliderElement extends RenderObjectElement { @override _RenderRangeSlider get renderObject => super.renderObject; - void _mountChild(Widget newWidget, _ChildElements slot) { + void _mountChild(Widget newWidget, ChildElements slot) { final Element oldChild = _slotToChild[slot]; final Element newChild = updateChild(oldChild, newWidget, slot); if (oldChild != null) { @@ -1389,7 +1474,7 @@ class _RenderRangeSliderElement extends RenderObjectElement { } } - void _updateChild(Widget widget, _ChildElements slot) { + void _updateChild(Widget widget, ChildElements slot) { final Element oldChild = _slotToChild[slot]; final Element newChild = updateChild(oldChild, widget, slot); if (oldChild != null) { @@ -1402,15 +1487,15 @@ class _RenderRangeSliderElement extends RenderObjectElement { } } - void _updateRenderObject(RenderObject child, _ChildElements slot) { + void _updateRenderObject(RenderObject child, ChildElements slot) { switch (slot) { - case _ChildElements.startThumbIcon: + case ChildElements.startThumbIcon: renderObject.startThumbIcon = child; break; - case _ChildElements.endThumbIcon: + case ChildElements.endThumbIcon: renderObject.endThumbIcon = child; break; - case _ChildElements.child: + case ChildElements.child: break; } } @@ -1423,30 +1508,30 @@ class _RenderRangeSliderElement extends RenderObjectElement { @override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); - _mountChild(widget.startThumbIcon, _ChildElements.startThumbIcon); - _mountChild(widget.endThumbIcon, _ChildElements.endThumbIcon); + _mountChild(widget.startThumbIcon, ChildElements.startThumbIcon); + _mountChild(widget.endThumbIcon, ChildElements.endThumbIcon); } @override void update(_RangeSliderRenderObjectWidget newWidget) { super.update(newWidget); assert(widget == newWidget); - _updateChild(widget.startThumbIcon, _ChildElements.startThumbIcon); - _updateChild(widget.endThumbIcon, _ChildElements.endThumbIcon); + _updateChild(widget.startThumbIcon, ChildElements.startThumbIcon); + _updateChild(widget.endThumbIcon, ChildElements.endThumbIcon); } @override - void insertChildRenderObject(RenderObject child, dynamic slotValue) { + void insertRenderObjectChild(RenderObject child, dynamic slotValue) { assert(child is RenderBox); - assert(slotValue is _ChildElements); - final _ChildElements slot = slotValue; + assert(slotValue is ChildElements); + final ChildElements slot = slotValue; _updateRenderObject(child, slot); assert(renderObject.childToSlot.keys.contains(child)); assert(renderObject.slotToChild.keys.contains(slot)); } @override - void removeChildRenderObject(RenderObject child) { + void removeRenderObjectChild(RenderObject child, dynamic slot) { assert(child is RenderBox); assert(renderObject.childToSlot.keys.contains(child)); _updateRenderObject(null, renderObject.childToSlot[child]); @@ -1455,12 +1540,13 @@ class _RenderRangeSliderElement extends RenderObjectElement { } @override - void moveChildRenderObject(RenderObject child, dynamic slotValue) { + void moveRenderObjectChild( + RenderObject child, dynamic oldSlot, dynamic newSlot) { assert(false, 'not reachable'); } } -class _RenderRangeSlider extends _RenderBaseSlider { +class _RenderRangeSlider extends RenderBaseSlider { _RenderRangeSlider({ dynamic min, dynamic max, @@ -1473,7 +1559,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { bool showTicks, bool showLabels, bool showDivisors, - bool showTooltip, + bool enableTooltip, bool enableIntervalSelection, Color inactiveColor, Color activeColor, @@ -1506,7 +1592,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { showTicks: showTicks, showLabels: showLabels, showDivisors: showDivisors, - showTooltip: showTooltip, + enableTooltip: enableTooltip, labelPlacement: labelPlacement, numberFormat: numberFormat, dateFormat: dateFormat, @@ -1561,7 +1647,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { values.end.millisecondsSinceEpoch.toDouble()); } unformattedLabels = []; - _updateTextPainter(); + updateTextPainter(); if (_enableIntervalSelection) { _state.startPositionController.value = @@ -1571,11 +1657,11 @@ class _RenderRangeSlider extends _RenderBaseSlider { } final _SfRangeSliderState _state; - final Map<_ChildElements, RenderBox> slotToChild = - <_ChildElements, RenderBox>{}; + final Map slotToChild = + {}; - final Map childToSlot = - {}; + final Map childToSlot = + {}; Animation _overlayStartAnimation; @@ -1680,14 +1766,14 @@ class _RenderRangeSlider extends _RenderBaseSlider { RenderBox _startThumbIcon; set startThumbIcon(RenderBox value) { _startThumbIcon = - _updateChild(_startThumbIcon, value, _ChildElements.startThumbIcon); + _updateChild(_startThumbIcon, value, ChildElements.startThumbIcon); } RenderBox get endThumbIcon => _endThumbIcon; RenderBox _endThumbIcon; set endThumbIcon(RenderBox value) { _endThumbIcon = - _updateChild(_endThumbIcon, value, _ChildElements.endThumbIcon); + _updateChild(_endThumbIcon, value, ChildElements.endThumbIcon); } bool get isInteractive => onChanged != null; @@ -1709,7 +1795,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { } void _onTapDown(TapDownDetails details) { - currentPointerType = _PointerType.down; + currentPointerType = PointerType.down; _interactionStartX = globalToLocal(details.globalPosition).dx; currentX = _interactionStartX; _beginInteraction(); @@ -1727,7 +1813,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { void _onDragUpdate(DragUpdateDetails details) { isInteractionEnd = false; - currentPointerType = _PointerType.move; + currentPointerType = PointerType.move; currentX = globalToLocal(details.globalPosition).dx; _updateRangeValues(); markNeedsPaint(); @@ -1781,7 +1867,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { } void _forwardTooltipAnimation() { - if (_showTooltip) { + if (enableTooltip) { willDrawTooltip = true; _state.tooltipAnimationController.forward(); _state.tooltipDelayTimer?.cancel(); @@ -1827,13 +1913,13 @@ class _RenderRangeSlider extends _RenderBaseSlider { final double startValue = math.min(value, end - minThumbGap); final dynamic actualStartValue = getActualValue(valueInDouble: startValue); - newValues = values._copyWith(start: actualStartValue); + newValues = values.copyWith(start: actualStartValue); break; case SfThumb.end: final double endValue = math.max(value, start + minThumbGap); final dynamic actualEndValue = getActualValue(valueInDouble: endValue); - newValues = values._copyWith(end: actualEndValue); + newValues = values.copyWith(end: actualEndValue); break; } @@ -1861,10 +1947,10 @@ class _RenderRangeSlider extends _RenderBaseSlider { } _isDragging = false; - currentPointerType = _PointerType.up; + currentPointerType = PointerType.up; _state.overlayStartController.reverse(); _state.overlayEndController.reverse(); - if (_showTooltip && _state.tooltipDelayTimer == null) { + if (enableTooltip && _state.tooltipDelayTimer == null) { _state.tooltipAnimationController.reverse(); } @@ -1927,7 +2013,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { start = isDateTime ? DateTime.fromMillisecondsSinceEpoch(currentLabel.toInt()) : currentLabel; - end = _max; + end = max; rangeValues = SfRangeValues(start, end); } } @@ -1935,7 +2021,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { } RenderBox _updateChild( - RenderBox oldChild, RenderBox newChild, _ChildElements slot) { + RenderBox oldChild, RenderBox newChild, ChildElements slot) { if (oldChild != null) { dropChild(oldChild); childToSlot.remove(oldChild); @@ -1969,10 +2055,10 @@ class _RenderRangeSlider extends _RenderBaseSlider { thumbShape.paint(context, thumbCenter, parentBox: this, child: thumbIcon, - themeData: _sliderThemeData, + themeData: sliderThemeData, currentValues: _values, enableAnimation: _stateAnimation, - textDirection: _textDirection, + textDirection: textDirection, thumb: isLeftThumbActive ? SfThumb.end : SfThumb.start); thumbCenter = isLeftThumbActive ? startThumbCenter : endThumbCenter; @@ -1980,7 +2066,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { // Drawing overlay. overlayShape.paint(context, thumbCenter, parentBox: this, - themeData: _sliderThemeData, + themeData: sliderThemeData, currentValues: _values, animation: isLeftThumbActive ? _overlayStartAnimation : _overlayEndAnimation, @@ -1996,10 +2082,10 @@ class _RenderRangeSlider extends _RenderBaseSlider { thumbShape.paint(context, thumbCenter, parentBox: this, child: thumbIcon, - themeData: _sliderThemeData, + themeData: sliderThemeData, currentValues: _values, enableAnimation: _stateAnimation, - textDirection: _textDirection, + textDirection: textDirection, thumb: activeThumb); } @@ -2012,7 +2098,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { Rect trackRect) { if (willDrawTooltip) { final Paint paint = Paint() - ..color = _sliderThemeData.tooltipBackgroundColor + ..color = sliderThemeData.tooltipBackgroundColor ..style = PaintingStyle.fill ..strokeWidth = 0; @@ -2022,7 +2108,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { String tooltipText = tooltipTextFormatterCallback( actualText, getFormattedText(actualText)); TextSpan textSpan = - TextSpan(text: tooltipText, style: _sliderThemeData.tooltipTextStyle); + TextSpan(text: tooltipText, style: sliderThemeData.tooltipTextStyle); textPainter.text = textSpan; textPainter.layout(); @@ -2048,7 +2134,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { tooltipShape.paint(context, thumbCenter, Offset(actualTrackOffset.dx, tooltipStartY), textPainter, parentBox: this, - sliderThemeData: _sliderThemeData, + sliderThemeData: sliderThemeData, paint: paint, animation: _tooltipAnimation, trackRect: trackRect); @@ -2059,7 +2145,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { tooltipText = tooltipTextFormatterCallback( actualText, getFormattedText(actualText)); textSpan = - TextSpan(text: tooltipText, style: _sliderThemeData.tooltipTextStyle); + TextSpan(text: tooltipText, style: sliderThemeData.tooltipTextStyle); textPainter.text = textSpan; textPainter.layout(); @@ -2086,7 +2172,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { tooltipShape.paint(context, thumbCenter, Offset(actualTrackOffset.dx, tooltipStartY), textPainter, parentBox: this, - sliderThemeData: _sliderThemeData, + sliderThemeData: sliderThemeData, paint: paint, animation: _tooltipAnimation, trackRect: trackRect); @@ -2210,7 +2296,7 @@ class _RenderRangeSlider extends _RenderBaseSlider { // Drawing track. final Rect trackRect = - trackShape.getPreferredRect(this, _sliderThemeData, actualTrackOffset); + trackShape.getPreferredRect(this, sliderThemeData, actualTrackOffset); final double thumbStartPosition = getFactorFromValue(_isIntervalTapped ? getValueFromFactor(_state.startPositionController.value) : actualValues.start) * @@ -2227,10 +2313,10 @@ class _RenderRangeSlider extends _RenderBaseSlider { trackShape.paint( context, actualTrackOffset, null, startThumbCenter, endThumbCenter, parentBox: this, - themeData: _sliderThemeData, + themeData: sliderThemeData, currentValues: _values, enableAnimation: _stateAnimation, - textDirection: _textDirection); + textDirection: textDirection); if (showLabels || showTicks || showDivisors) { drawLabelsTicksAndDivisors(context, trackRect, offset, null, @@ -2270,4 +2356,27 @@ class _RenderRangeSlider extends _RenderBaseSlider { } } } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(StringProperty( + 'thumbSize', thumbShape.getPreferredSize(sliderThemeData).toString())); + properties.add(StringProperty( + 'activeDivisorSize', + divisorShape + .getPreferredSize(sliderThemeData, isActive: true) + .toString())); + properties.add(StringProperty( + 'inactiveDivisorSize', + divisorShape + .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())); + } } diff --git a/packages/syncfusion_flutter_sliders/lib/src/render_slider_base.dart b/packages/syncfusion_flutter_sliders/lib/src/render_slider_base.dart index c72430c81..d06e87ad7 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/render_slider_base.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/render_slider_base.dart @@ -1,8 +1,20 @@ -part of sliders; +import 'dart:math' as math; +import 'dart:ui'; -class _RenderBaseSlider extends RenderProxyBox +import 'package:flutter/gestures.dart' + show TapGestureRecognizer, HorizontalDragGestureRecognizer; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show DateFormat, NumberFormat; +import 'package:syncfusion_flutter_core/theme.dart'; + +import 'common.dart'; +import 'constants.dart'; +import 'slider_shapes.dart'; + +class RenderBaseSlider extends RenderProxyBox with RelayoutWhenSystemFontsChangeMixin { - _RenderBaseSlider({ + RenderBaseSlider({ dynamic min, dynamic max, double interval, @@ -12,7 +24,7 @@ class _RenderBaseSlider extends RenderProxyBox bool showTicks, bool showLabels, bool showDivisors, - bool showTooltip, + bool enableTooltip, LabelPlacement labelPlacement, NumberFormat numberFormat, DateFormat dateFormat, @@ -38,7 +50,7 @@ class _RenderBaseSlider extends RenderProxyBox _showTicks = showTicks, _showLabels = showLabels, _showDivisors = showDivisors, - _showTooltip = showTooltip, + _enableTooltip = enableTooltip, _labelPlacement = labelPlacement, _numberFormat = numberFormat, _dateFormat = dateFormat, @@ -68,7 +80,7 @@ class _RenderBaseSlider extends RenderProxyBox _minorTickPositions = []; thumbElevationTween = - Tween(begin: _defaultElevation, end: _tappedElevation); + Tween(begin: defaultElevation, end: tappedElevation); } final double minTrackWidth = kMinInteractiveDimension * 3; @@ -118,7 +130,7 @@ class _RenderBaseSlider extends RenderProxyBox Tween thumbElevationTween; - _PointerType currentPointerType; + PointerType currentPointerType; dynamic get min => _min; dynamic _min; @@ -223,13 +235,13 @@ class _RenderBaseSlider extends RenderProxyBox markNeedsLayout(); } - bool get showTooltip => _showTooltip; - bool _showTooltip; - set showTooltip(bool value) { - if (_showTooltip == value) { + bool get enableTooltip => _enableTooltip; + bool _enableTooltip; + set enableTooltip(bool value) { + if (_enableTooltip == value) { return; } - _showTooltip = value; + _enableTooltip = value; } LabelPlacement get labelPlacement => _labelPlacement; @@ -392,7 +404,7 @@ class _RenderBaseSlider extends RenderProxyBox return; } _textDirection = value; - _updateTextPainter(); + updateTextPainter(); markNeedsPaint(); } @@ -404,7 +416,7 @@ class _RenderBaseSlider extends RenderProxyBox return; } _mediaQueryData = value; - _updateTextPainter(); + updateTextPainter(); markNeedsLayout(); } @@ -466,7 +478,7 @@ class _RenderBaseSlider extends RenderProxyBox ? _stepDuration ?? adjustmentUnit : _stepSize ?? adjustmentUnit; - void _updateTextPainter() { + void updateTextPainter() { textPainter ..textDirection = _textDirection ..textScaleFactor = _mediaQueryData.textScaleFactor; @@ -795,13 +807,13 @@ class _RenderBaseSlider extends RenderProxyBox Rect getRectangularTooltipRect(TextPainter textPainter, Offset offset, Offset thumbCenter, Rect trackRect, SfSliderThemeData themeData) { final double rectangularTooltipHeight = - textPainter.height + _tooltipTextPadding.dy > _minTooltipHeight - ? textPainter.height + _tooltipTextPadding.dy - : _minTooltipHeight; + textPainter.height + tooltipTextPadding.dy > minTooltipHeight + ? textPainter.height + tooltipTextPadding.dy + : minTooltipHeight; final double halfTextWidth = - textPainter.width + _tooltipTextPadding.dx > _minTooltipWidth - ? (textPainter.width + _tooltipTextPadding.dx) / 2 - : _minTooltipWidth / 2; + textPainter.width + tooltipTextPadding.dx > minTooltipWidth + ? (textPainter.width + tooltipTextPadding.dx) / 2 + : minTooltipWidth / 2; double rightLineWidth = thumbCenter.dx + halfTextWidth > trackRect.right ? trackRect.right - thumbCenter.dx @@ -818,7 +830,7 @@ class _RenderBaseSlider extends RenderProxyBox final double top = thumbCenter.dy - rectangularTooltipHeight - offset.dy - - _tooltipTriangleHeight; + tooltipTriangleHeight; final double bottom = thumbCenter.dy - offset.dy; return Rect.fromLTRB(left, top, right, bottom); @@ -827,15 +839,15 @@ class _RenderBaseSlider extends RenderProxyBox Rect getPaddleTooltipRect(TextPainter textPainter, Offset offset, Offset thumbCenter, Rect trackRect, SfSliderThemeData themeData) { final double paddleTooltipRadius = - textPainter.height > _minPaddleTopCircleRadius + textPainter.height > minPaddleTopCircleRadius ? textPainter.height - : _minPaddleTopCircleRadius; - final double topNeckRadius = paddleTooltipRadius - _neckDifference; + : minPaddleTopCircleRadius; + final double topNeckRadius = paddleTooltipRadius - neckDifference; final double bottomNeckRadius = - themeData.thumbRadius > _minPaddleTopCircleRadius * _moveNeckValue - ? themeData.thumbRadius - _neckDifference + themeData.thumbRadius > minPaddleTopCircleRadius * moveNeckValue + ? themeData.thumbRadius - neckDifference : 4.0; - final double halfTextWidth = textPainter.width / 2 + _textPadding; + final double halfTextWidth = textPainter.width / 2 + textPadding; final double halfPaddleWidth = halfTextWidth > paddleTooltipRadius ? halfTextWidth : paddleTooltipRadius; @@ -845,9 +857,9 @@ class _RenderBaseSlider extends RenderProxyBox final double right = thumbCenter.dx + halfPaddleWidth - shift; final double top = thumbCenter.dy - paddleTooltipRadius - - paddleTooltipRadius * (1.0 - _moveNeckValue) - + paddleTooltipRadius * (1.0 - moveNeckValue) - topNeckRadius - - offset.dy * (1.0 - _moveNeckValue) - + offset.dy * (1.0 - moveNeckValue) - bottomNeckRadius; final double bottom = thumbCenter.dy + themeData.thumbRadius; return Rect.fromLTRB(left, top, right, bottom); @@ -1006,7 +1018,9 @@ class _RenderBaseSlider extends RenderProxyBox // interval as _max - _min. final double intervalDiff = isDateTime ? 1 - : _interval != null && _interval > 0 ? _interval : _max - _min; + : _interval != null && _interval > 0 + ? _interval + : _max - _min; textValue += intervalDiff; dateTimePos += 1; } @@ -1236,7 +1250,7 @@ class _RenderBaseSlider extends RenderProxyBox void systemFontsDidChange() { super.systemFontsDidChange(); textPainter.markNeedsLayout(); - _updateTextPainter(); + updateTextPainter(); } @override diff --git a/packages/syncfusion_flutter_sliders/lib/src/slider.dart b/packages/syncfusion_flutter_sliders/lib/src/slider.dart index b19b617be..cc7d16580 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/slider.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/slider.dart @@ -1,4 +1,21 @@ -part of sliders; +import 'dart:async'; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/foundation.dart'; +import 'package:intl/intl.dart' show DateFormat, NumberFormat; +import 'package:flutter/material.dart'; +import 'package:flutter/gestures.dart' + show + GestureArenaTeam, + TapGestureRecognizer, + HorizontalDragGestureRecognizer; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import 'common.dart'; +import 'constants.dart'; +import 'render_slider_base.dart'; +import 'slider_shapes.dart'; /// A Material Design slider. /// @@ -92,6 +109,7 @@ part of sliders; /// date labels. /// * [SfSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfSliderThemeData-class.html), for customizing the visual appearance of the slider. class SfSlider extends StatefulWidget { + /// Creates a [SfSlider]. const SfSlider( {Key key, this.min = 0.0, @@ -105,7 +123,7 @@ class SfSlider extends StatefulWidget { this.showTicks = false, this.showLabels = false, this.showDivisors = false, - this.showTooltip = false, + this.enableTooltip = false, this.activeColor, this.inactiveColor, this.labelPlacement, @@ -334,8 +352,8 @@ class SfSlider extends StatefulWidget { /// min: DateTime(2015, 01, 01), /// max: DateTime(2020, 01, 01), /// value: _value, - /// showTooltip: true, - /// stepDuration: SliderDuration(years: 1, months: 6), + /// enableTooltip: true, + /// stepDuration: SliderDuration(years: 1, months: 6), /// interval: 2, /// showLabels: true, /// showTicks: true, @@ -503,7 +521,7 @@ class SfSlider extends StatefulWidget { /// By default, tooltip text is formatted with either [numberFormat] or /// [dateFormat]. /// - /// This snippet shows how to show tooltip in [SfSlider]. + /// This snippet shows how to enable tooltip in [SfSlider]. /// /// ```dart /// double _value = 4.0; @@ -515,7 +533,7 @@ class SfSlider extends StatefulWidget { /// interval: 1, /// showTicks: true, /// showLabels: true, - /// showTooltip: true, + /// enableTooltip: true, /// onChanged: (dynamic newValue) { /// setState(() { /// _value = newValue; @@ -528,7 +546,7 @@ class SfSlider extends StatefulWidget { /// /// * [tooltipTextFormatterCallback], for changing the default tooltip text. /// * [SfSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfSliderThemeData-class.html), for customizing the appearance of the tooltip text. - final bool showTooltip; + final bool enableTooltip; /// Color applied to the inactive track and active divisors. /// @@ -768,7 +786,7 @@ class SfSlider extends StatefulWidget { /// value: _value, /// interval: 4, /// showLabels: true, - /// showTooltip: true, + /// enableTooltip: true, /// dateFormat: DateFormat('h a'), /// dateIntervalType: DateIntervalType.hours, /// tooltipTextFormatterCallback: @@ -845,7 +863,7 @@ class SfSlider extends StatefulWidget { /// showLabels: true, /// showTicks: true, /// interval: 20, - /// showTooltip: true, + /// enableTooltip: true, /// tooltipShape: SfPaddleTooltipShape(), /// onChanged: (dynamic newValue) { /// setState(() { @@ -861,7 +879,8 @@ class SfSlider extends StatefulWidget { /// Defaults to `null`. /// /// It is possible to set any widget inside the thumb. If the widget - /// exceeds the size of the thumb, increase the [thumbRadius] based on it. + /// exceeds the size of the thumb, increase the + /// [SfSliderThemeData.thumbRadius] based on it. /// /// This snippet shows how to show thumb icon in [SfSlider]. /// @@ -875,7 +894,7 @@ class SfSlider extends StatefulWidget { /// interval: 1, /// showTicks: true, /// showLabels: true, - /// showTooltip: true, + /// enableTooltip: true, /// onChanged: (dynamic newValue) { /// setState(() { /// _value = newValue; @@ -897,6 +916,61 @@ class SfSlider extends StatefulWidget { @override _SfSliderState createState() => _SfSliderState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('value', value)); + properties.add(DiagnosticsProperty('min', min)); + properties.add(DiagnosticsProperty('max', max)); + properties.add(ObjectFlagProperty>( + 'onChanged', onChanged, + ifNull: 'disabled')); + 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', + value: showTicks, + ifTrue: 'Ticks are showing', + ifFalse: 'Ticks are not showing', + showName: false)); + properties.add(FlagProperty('showLabels', + value: showLabels, + ifTrue: 'Labels are showing', + ifFalse: 'Labels are not showing', + showName: false)); + properties.add(FlagProperty('showDivisors', + value: showDivisors, + ifTrue: 'Divisors are showing', + ifFalse: 'Divisors are not showing', + showName: false)); + 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)); + if (value.runtimeType == DateTime) { + properties.add(StringProperty('dateFormat', + 'Formatted value is ' + (dateFormat.format(value.start)).toString())); + } + properties.add( + EnumProperty('dateIntervalType', dateIntervalType)); + properties.add(ObjectFlagProperty.has( + 'tooltipTextFormatterCallback', tooltipTextFormatterCallback)); + properties.add(ObjectFlagProperty.has( + 'labelFormatterCallback', labelFormatterCallback)); + properties.add(ObjectFlagProperty>.has( + 'semanticFormatterCallback', semanticFormatterCallback)); + } } class _SfSliderState extends State with TickerProviderStateMixin { @@ -1052,7 +1126,7 @@ class _SfSliderState extends State with TickerProviderStateMixin { showTicks: widget.showTicks, showLabels: widget.showLabels, showDivisors: widget.showDivisors, - showTooltip: widget.showTooltip, + enableTooltip: widget.enableTooltip, inactiveColor: widget.inactiveColor ?? themeData.primaryColor.withOpacity(0.24), activeColor: widget.activeColor ?? themeData.primaryColor, @@ -1065,12 +1139,12 @@ class _SfSliderState extends State with TickerProviderStateMixin { tooltipTextFormatterCallback: widget.tooltipTextFormatterCallback ?? _getFormattedTooltipText, semanticFormatterCallback: widget.semanticFormatterCallback, - trackShape: widget.trackShape ?? _SfTrackShape(), - divisorShape: widget.divisorShape ?? _SfDivisorShape(), - overlayShape: widget.overlayShape ?? _SfOverlayShape(), - thumbShape: widget.thumbShape ?? _SfThumbShape(), - tickShape: widget.tickShape ?? _SfTickShape(), - minorTickShape: widget.minorTickShape ?? _SfMinorTickShape(), + trackShape: widget.trackShape ?? SfTrackShape(), + divisorShape: widget.divisorShape ?? SfDivisorShape(), + overlayShape: widget.overlayShape ?? SfOverlayShape(), + thumbShape: widget.thumbShape ?? SfThumbShape(), + tickShape: widget.tickShape ?? SfTickShape(), + minorTickShape: widget.minorTickShape ?? SfMinorTickShape(), tooltipShape: widget.tooltipShape ?? SfRectangularTooltipShape(), sliderThemeData: _getSliderThemeData(themeData, isActive), thumbIcon: widget.thumbIcon, @@ -1092,7 +1166,7 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { this.showTicks, this.showLabels, this.showDivisors, - this.showTooltip, + this.enableTooltip, this.inactiveColor, this.activeColor, this.labelPlacement, @@ -1126,7 +1200,7 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { final bool showTicks; final bool showLabels; final bool showDivisors; - final bool showTooltip; + final bool enableTooltip; final Color inactiveColor; final Color activeColor; @@ -1166,7 +1240,7 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { showTicks: showTicks, showLabels: showLabels, showDivisors: showDivisors, - showTooltip: showTooltip, + enableTooltip: enableTooltip, inactiveColor: inactiveColor, activeColor: activeColor, labelPlacement: labelPlacement, @@ -1204,7 +1278,7 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { ..showTicks = showTicks ..showLabels = showLabels ..showDivisors = showDivisors - ..showTooltip = showTooltip + ..enableTooltip = enableTooltip ..inactiveColor = inactiveColor ..activeColor = activeColor ..labelPlacement = labelPlacement @@ -1230,9 +1304,9 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { class _RenderSliderElement extends RenderObjectElement { _RenderSliderElement(_SliderRenderObjectWidget slider) : super(slider); - final Map<_ChildElements, Element> _slotToChild = <_ChildElements, Element>{}; + final Map _slotToChild = {}; - final Map _childToSlot = {}; + final Map _childToSlot = {}; @override _SliderRenderObjectWidget get widget => super.widget; @@ -1240,7 +1314,7 @@ class _RenderSliderElement extends RenderObjectElement { @override _RenderSlider get renderObject => super.renderObject; - void _mountChild(Widget newWidget, _ChildElements slot) { + void _mountChild(Widget newWidget, ChildElements slot) { final Element oldChild = _slotToChild[slot]; final Element newChild = updateChild(oldChild, newWidget, slot); if (oldChild != null) { @@ -1253,7 +1327,7 @@ class _RenderSliderElement extends RenderObjectElement { } } - void _updateChild(Widget widget, _ChildElements slot) { + void _updateChild(Widget widget, ChildElements slot) { final Element oldChild = _slotToChild[slot]; final Element newChild = updateChild(oldChild, widget, slot); if (oldChild != null) { @@ -1266,14 +1340,14 @@ class _RenderSliderElement extends RenderObjectElement { } } - void _updateRenderObject(RenderObject child, _ChildElements slot) { + void _updateRenderObject(RenderObject child, ChildElements slot) { switch (slot) { - case _ChildElements.startThumbIcon: + case ChildElements.startThumbIcon: renderObject.thumbIcon = child; break; - case _ChildElements.endThumbIcon: + case ChildElements.endThumbIcon: break; - case _ChildElements.child: + case ChildElements.child: break; } } @@ -1286,28 +1360,28 @@ class _RenderSliderElement extends RenderObjectElement { @override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); - _mountChild(widget.thumbIcon, _ChildElements.startThumbIcon); + _mountChild(widget.thumbIcon, ChildElements.startThumbIcon); } @override void update(_SliderRenderObjectWidget newWidget) { super.update(newWidget); assert(widget == newWidget); - _updateChild(widget.thumbIcon, _ChildElements.startThumbIcon); + _updateChild(widget.thumbIcon, ChildElements.startThumbIcon); } @override - void insertChildRenderObject(RenderObject child, dynamic slotValue) { + void insertRenderObjectChild(RenderObject child, dynamic slotValue) { assert(child is RenderBox); - assert(slotValue is _ChildElements); - final _ChildElements slot = slotValue; + assert(slotValue is ChildElements); + final ChildElements slot = slotValue; _updateRenderObject(child, slot); assert(renderObject.childToSlot.keys.contains(child)); assert(renderObject.slotToChild.keys.contains(slot)); } @override - void removeChildRenderObject(RenderObject child) { + void removeRenderObjectChild(RenderObject child, dynamic slot) { assert(child is RenderBox); assert(renderObject.childToSlot.keys.contains(child)); _updateRenderObject(null, renderObject.childToSlot[child]); @@ -1316,12 +1390,13 @@ class _RenderSliderElement extends RenderObjectElement { } @override - void moveChildRenderObject(RenderObject child, dynamic slotValue) { + void moveRenderObjectChild( + RenderObject child, dynamic oldSlot, dynamic newSlot) { assert(false, 'not reachable'); } } -class _RenderSlider extends _RenderBaseSlider { +class _RenderSlider extends RenderBaseSlider { _RenderSlider({ dynamic min, dynamic max, @@ -1334,7 +1409,7 @@ class _RenderSlider extends _RenderBaseSlider { bool showTicks, bool showLabels, bool showDivisors, - bool showTooltip, + bool enableTooltip, Color inactiveColor, Color activeColor, LabelPlacement labelPlacement, @@ -1366,7 +1441,7 @@ class _RenderSlider extends _RenderBaseSlider { showTicks: showTicks, showLabels: showLabels, showDivisors: showDivisors, - showTooltip: showTooltip, + enableTooltip: enableTooltip, labelPlacement: labelPlacement, numberFormat: numberFormat, dateFormat: dateFormat, @@ -1411,7 +1486,7 @@ class _RenderSlider extends _RenderBaseSlider { _tooltipAnimation = CurvedAnimation( parent: _state.tooltipAnimationController, curve: Curves.fastOutSlowIn); - _updateTextPainter(); + updateTextPainter(); if (isDateTime) { _valueInMilliseconds = value.millisecondsSinceEpoch.toDouble(); @@ -1428,11 +1503,11 @@ class _RenderSlider extends _RenderBaseSlider { double _valueInMilliseconds; - final Map<_ChildElements, RenderBox> slotToChild = - <_ChildElements, RenderBox>{}; + final Map slotToChild = + {}; - final Map childToSlot = - {}; + final Map childToSlot = + {}; dynamic get value => _value; dynamic _value; @@ -1506,7 +1581,7 @@ class _RenderSlider extends _RenderBaseSlider { RenderBox _thumbIcon; set thumbIcon(RenderBox value) { - _thumbIcon = _updateChild(_thumbIcon, value, _ChildElements.startThumbIcon); + _thumbIcon = _updateChild(_thumbIcon, value, ChildElements.startThumbIcon); } bool get isInteractive => onChanged != null; @@ -1521,7 +1596,7 @@ class _RenderSlider extends _RenderBaseSlider { } RenderBox _updateChild( - RenderBox oldChild, RenderBox newChild, _ChildElements slot) { + RenderBox oldChild, RenderBox newChild, ChildElements slot) { if (oldChild != null) { dropChild(oldChild); childToSlot.remove(oldChild); @@ -1536,7 +1611,7 @@ class _RenderSlider extends _RenderBaseSlider { } void _onTapDown(TapDownDetails details) { - currentPointerType = _PointerType.down; + currentPointerType = PointerType.down; currentX = globalToLocal(details.globalPosition).dx; _beginInteraction(); } @@ -1552,7 +1627,7 @@ class _RenderSlider extends _RenderBaseSlider { void _onDragUpdate(DragUpdateDetails details) { isInteractionEnd = false; - currentPointerType = _PointerType.move; + currentPointerType = PointerType.move; currentX = globalToLocal(details.globalPosition).dx; _updateValue(); markNeedsPaint(); @@ -1569,7 +1644,7 @@ class _RenderSlider extends _RenderBaseSlider { void _beginInteraction() { isInteractionEnd = false; _state.overlayController.forward(); - if (_showTooltip) { + if (enableTooltip) { willDrawTooltip = true; _state.tooltipAnimationController.forward(); _state.tooltipDelayTimer?.cancel(); @@ -1601,7 +1676,7 @@ class _RenderSlider extends _RenderBaseSlider { void _endInteraction() { if (!isInteractionEnd) { _state.overlayController.reverse(); - if (_showTooltip && _state.tooltipDelayTimer == null) { + if (enableTooltip && _state.tooltipDelayTimer == null) { _state.tooltipAnimationController.reverse(); if (_state.tooltipAnimationController.status == AnimationStatus.dismissed) { @@ -1609,7 +1684,7 @@ class _RenderSlider extends _RenderBaseSlider { } } - currentPointerType = _PointerType.up; + currentPointerType = PointerType.up; isInteractionEnd = true; markNeedsPaint(); } @@ -1625,7 +1700,7 @@ class _RenderSlider extends _RenderBaseSlider { Offset actualTrackOffset, Rect trackRect) { if (willDrawTooltip) { final Paint paint = Paint() - ..color = _sliderThemeData.tooltipBackgroundColor + ..color = sliderThemeData.tooltipBackgroundColor ..style = PaintingStyle.fill ..strokeWidth = 0; @@ -1634,14 +1709,14 @@ class _RenderSlider extends _RenderBaseSlider { final String tooltipText = tooltipTextFormatterCallback( actualText, getFormattedText(actualText)); final TextSpan textSpan = - TextSpan(text: tooltipText, style: _sliderThemeData.tooltipTextStyle); + TextSpan(text: tooltipText, style: sliderThemeData.tooltipTextStyle); textPainter.text = textSpan; textPainter.layout(); tooltipShape.paint(context, thumbCenter, Offset(actualTrackOffset.dx, tooltipStartY), textPainter, parentBox: this, - sliderThemeData: _sliderThemeData, + sliderThemeData: sliderThemeData, paint: paint, animation: _tooltipAnimation, trackRect: trackRect); @@ -1713,7 +1788,7 @@ class _RenderSlider extends _RenderBaseSlider { // Drawing track. final Rect trackRect = - trackShape.getPreferredRect(this, _sliderThemeData, actualTrackOffset); + trackShape.getPreferredRect(this, sliderThemeData, actualTrackOffset); final double thumbPosition = getFactorFromValue(actualValue) * trackRect.width; final Offset thumbCenter = @@ -1722,9 +1797,9 @@ class _RenderSlider extends _RenderBaseSlider { trackShape.paint(context, actualTrackOffset, thumbCenter, null, null, parentBox: this, currentValue: _value, - themeData: _sliderThemeData, + themeData: sliderThemeData, enableAnimation: _stateAnimation, - textDirection: _textDirection); + textDirection: textDirection); if (showLabels || showTicks || showDivisors) { drawLabelsTicksAndDivisors(context, trackRect, offset, thumbCenter, null, @@ -1735,7 +1810,7 @@ class _RenderSlider extends _RenderBaseSlider { overlayShape.paint(context, thumbCenter, parentBox: this, currentValue: _value, - themeData: _sliderThemeData, + themeData: sliderThemeData, animation: _overlayAnimation); // Drawing thumb. @@ -1743,9 +1818,9 @@ class _RenderSlider extends _RenderBaseSlider { parentBox: this, child: _thumbIcon, currentValue: _value, - themeData: _sliderThemeData, + themeData: sliderThemeData, enableAnimation: _stateAnimation, - textDirection: _textDirection); + textDirection: textDirection); _drawTooltip(context, thumbCenter, offset, actualTrackOffset, trackRect); } @@ -1774,4 +1849,27 @@ class _RenderSlider extends _RenderBaseSlider { } } } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(StringProperty( + 'thumbSize', thumbShape.getPreferredSize(sliderThemeData).toString())); + properties.add(StringProperty( + 'activeDivisorSize', + divisorShape + .getPreferredSize(sliderThemeData, isActive: true) + .toString())); + properties.add(StringProperty( + 'inactiveDivisorSize', + divisorShape + .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())); + } } diff --git a/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart b/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart index e3cb7c67e..b3e5a213e 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart @@ -1,7 +1,17 @@ -part of sliders; - -/// Base class for [SfRangeSlider] and [SfRangeSelector] track shapes. -abstract class SfTrackShape { +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import 'common.dart'; +import 'constants.dart'; +import 'render_slider_base.dart'; + +/// Base class for [SfSlider], [SfRangeSlider] and [SfRangeSelector] track +/// shapes. +class SfTrackShape { + /// Enables subclasses to provide constant constructors. const SfTrackShape(); /// Returns the size based on the values passed to it. @@ -10,22 +20,24 @@ abstract class SfTrackShape { {bool isActive}) { final double maxRadius = math.max(themeData.overlayRadius, math.max(themeData.thumbRadius, themeData.tickSize.width / 2)); + final double maxTrackHeight = + math.max(themeData.activeTrackHeight, themeData.inactiveTrackHeight); final double left = offset.dx + maxRadius; double top = offset.dy; if (isActive != null) { - final double maxTrackHeight = - math.max(themeData.activeTrackHeight, themeData.inactiveTrackHeight); top += isActive ? (maxTrackHeight - themeData.activeTrackHeight) / 2 : (maxTrackHeight - themeData.inactiveTrackHeight) / 2; } - final double width = parentBox.size.width - 2 * maxRadius; - final double height = isActive == null - ? math.max(themeData.activeTrackHeight, themeData.inactiveTrackHeight) - : (isActive - ? themeData.activeTrackHeight - : themeData.inactiveTrackHeight); - return Rect.fromLTWH(left, top, width, height); + final double right = left + parentBox.size.width - (2 * maxRadius); + final double bottom = top + + (isActive == null + ? maxTrackHeight + : (isActive + ? themeData.activeTrackHeight + : themeData.inactiveTrackHeight)); + return Rect.fromLTRB( + math.min(left, right), top, math.max(left, right), bottom); } /// Paints the track based on the values passed to it. @@ -90,26 +102,46 @@ abstract class SfTrackShape { Radius radius, PaintingContext context, Rect activeTrackRect) { + Offset leftThumbCenter; + Offset rightThumbCenter; + Paint leftTrackPaint; + Paint rightTrackPaint; + Rect leftTrackRect; + Rect rightTrackRect; if (textDirection == TextDirection.rtl) { if (thumbCenter == null) { - final Offset temp = startThumbCenter; - startThumbCenter = endThumbCenter; - endThumbCenter = temp; + // For range slider and range selector widget. + leftThumbCenter = endThumbCenter; + rightThumbCenter = startThumbCenter; + } else { + // For slider widget. + leftTrackPaint = inactivePaint; + rightTrackPaint = activePaint; + leftTrackRect = inactiveTrackRect; + rightTrackRect = activeTrackRect; + } + } else { + if (thumbCenter == null) { + // For range slider and range selector widget. + leftThumbCenter = startThumbCenter; + rightThumbCenter = endThumbCenter; } else { - final Paint temp = activePaint; - activePaint = inactivePaint; - inactivePaint = temp; + // For slider widget. + leftTrackPaint = activePaint; + rightTrackPaint = inactivePaint; + leftTrackRect = activeTrackRect; + rightTrackRect = inactiveTrackRect; } } if (thumbCenter == null) { // Drawing range slider track. - _drawRangeSliderTrack(inactiveTrackRect, startThumbCenter, radius, - context, inactivePaint, activeTrackRect, endThumbCenter, activePaint); + _drawRangeSliderTrack(inactiveTrackRect, leftThumbCenter, radius, context, + inactivePaint, activeTrackRect, rightThumbCenter, activePaint); } else { // Drawing slider track. - _drawSliderTrack(activeTrackRect, thumbCenter, radius, context, - activePaint, inactiveTrackRect, inactivePaint); + _drawSliderTrack(leftTrackRect, thumbCenter, radius, context, + leftTrackPaint, rightTrackRect, rightTrackPaint); } } @@ -180,11 +212,13 @@ abstract class SfTrackShape { } } -/// Base class for [SfRangeSlider] and [SfRangeSelector] thumb shapes. -abstract class SfThumbShape { +/// Base class for [SfSlider], [SfRangeSlider] and [SfRangeSelector] thumb +/// shapes. +class SfThumbShape { + /// Enables subclasses to provide constant constructors. const SfThumbShape(); - bool _isThumbOverlap(_RenderBaseSlider parentBox) { + bool _isThumbOverlap(RenderBaseSlider parentBox) { return parentBox.showOverlappingThumbStroke; } @@ -211,20 +245,20 @@ abstract class SfThumbShape { themeData.thumbStrokeWidth > 0; final bool showThumbShadow = themeData.thumbColor != Colors.transparent; - final _RenderBaseSlider parentRenderBox = parentBox; + final RenderBaseSlider parentRenderBox = parentBox; if (showThumbShadow) { final Path path = Path(); final bool isThumbActive = (parentRenderBox.activeThumb == thumb || thumb == null) && parentRenderBox.currentPointerType != null && - parentRenderBox.currentPointerType != _PointerType.up; + parentRenderBox.currentPointerType != PointerType.up; path.addOval( Rect.fromCircle(center: center, radius: themeData.thumbRadius)); final double thumbElevation = isThumbActive ? parentRenderBox.thumbElevationTween.evaluate(enableAnimation) - : _defaultElevation; + : defaultElevation; - context.canvas.drawShadow(path, _shadowColor, thumbElevation, true); + context.canvas.drawShadow(path, shadowColor, thumbElevation, true); } if (!isThumbStroke && @@ -280,8 +314,10 @@ abstract class SfThumbShape { } } -/// Base class for [SfRangeSlider] and [SfRangeSelector] divisors shapes. -abstract class SfDivisorShape { +/// Base class for [SfSlider], [SfRangeSlider] and [SfRangeSelector] divisors +/// shapes. +class SfDivisorShape { + /// Enables subclasses to provide constant constructors. const SfDivisorShape(); /// Returns the size based on the values passed to it. @@ -362,8 +398,10 @@ abstract class SfDivisorShape { } } -/// Base class for [SfRangeSlider] and [SfRangeSelector] overlay shapes. -abstract class SfOverlayShape { +/// Base class for [SfSlider], [SfRangeSlider] and [SfRangeSelector] overlay +/// shapes. +class SfOverlayShape { + /// Enables subclasses to provide constant constructors. const SfOverlayShape(); /// Returns the size based on the values passed to it. @@ -391,8 +429,10 @@ abstract class SfOverlayShape { } } -/// Base class for [SfRangeSlider] and [SfRangeSelector] major tick shapes. -abstract class SfTickShape { +/// Base class for [SfSlider], [SfRangeSlider] and [SfRangeSelector] major tick +/// shapes. +class SfTickShape { + /// Enables subclasses to provide constant constructors. const SfTickShape(); /// Returns the size based on the values passed to it. @@ -442,6 +482,7 @@ abstract class SfTickShape { /// A base class which is used to render rectangular or paddle shape tooltip. abstract class SfTooltipShape { + /// Enables subclasses to provide constant constructors. const SfTooltipShape(); /// Draws the tooltip based on the values passed in the arguments. @@ -454,17 +495,7 @@ abstract class SfTooltipShape { Rect trackRect}); } -class _SfTrackShape extends SfTrackShape {} - -class _SfDivisorShape extends SfDivisorShape {} - -class _SfOverlayShape extends SfOverlayShape {} - -class _SfThumbShape extends SfThumbShape {} - -class _SfTickShape extends SfTickShape {} - -class _SfMinorTickShape extends SfTickShape { +class SfMinorTickShape extends SfTickShape { @override Size getPreferredSize(SfSliderThemeData themeData) { return Size.copy(themeData.minorTickSize); @@ -511,7 +542,7 @@ class _SfMinorTickShape extends SfTickShape { /// A class which is used to render paddle shape tooltip. class SfPaddleTooltipShape extends SfTooltipShape { - bool _isTooltipOverlapStroke(_RenderBaseSlider parentBox) { + bool _isTooltipOverlapStroke(RenderBaseSlider parentBox) { return parentBox.showOverlappingTooltipStroke; } @@ -671,7 +702,7 @@ class SfPaddleTooltipShape extends SfTooltipShape { 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( @@ -748,14 +779,14 @@ class SfPaddleTooltipShape extends SfTooltipShape { _drawPaddleTooltip( parentBox, textPainter, - _minPaddleTopCircleRadius, - _neckDifference, + minPaddleTopCircleRadius, + neckDifference, sliderThemeData, - _defaultThumbRadius, - _minBottomNeckRadius, - _textPadding, + defaultThumbRadius, + minBottomNeckRadius, + textPadding, offset, - _moveNeckValue, + moveNeckValue, thumbCenter, trackRect, context, @@ -766,18 +797,17 @@ class SfPaddleTooltipShape extends SfTooltipShape { /// A class which is used to render rectangular shape tooltip. class SfRectangularTooltipShape extends SfTooltipShape { - bool _isTooltipOverlapStroke(_RenderBaseSlider parentBox) { + bool _isTooltipOverlapStroke(RenderBaseSlider parentBox) { return parentBox.showOverlappingTooltipStroke; } Path _updateRectangularTooltipWidth( Size textSize, double tooltipStartY, Rect trackRect, double dx) { - final double dy = tooltipStartY + _tooltipTriangleHeight; + final double dy = tooltipStartY + tooltipTriangleHeight; final double tooltipWidth = - textSize.width < _minTooltipWidth ? _minTooltipWidth : textSize.width; - final double tooltipHeight = textSize.height < _minTooltipHeight - ? _minTooltipHeight - : textSize.height; + textSize.width < minTooltipWidth ? minTooltipWidth : textSize.width; + final double tooltipHeight = + textSize.height < minTooltipHeight ? minTooltipHeight : textSize.height; final double halfTooltipWidth = tooltipWidth / 2; double rightLineWidth = dx + halfTooltipWidth > trackRect.right @@ -789,7 +819,7 @@ class SfRectangularTooltipShape extends SfTooltipShape { rightLineWidth = leftLineWidth < halfTooltipWidth ? halfTooltipWidth - leftLineWidth + rightLineWidth : rightLineWidth; - const double halfTooltipTriangleWidth = _tooltipTriangleWidth / 2; + const double halfTooltipTriangleWidth = tooltipTriangleWidth / 2; return _getRectangularPath(tooltipStartY, rightLineWidth, halfTooltipTriangleWidth, dy, tooltipHeight, leftLineWidth); @@ -806,36 +836,36 @@ class SfRectangularTooltipShape extends SfTooltipShape { path.moveTo(0, -tooltipStartY); // / final bool canAdjustTooltipNose = - rightLineWidth > halfTooltipTriangleWidth + _cornerRadius / 2; + rightLineWidth > halfTooltipTriangleWidth + cornerRadius / 2; path.lineTo( canAdjustTooltipNose ? halfTooltipTriangleWidth : rightLineWidth, - -dy - (canAdjustTooltipNose ? 0 : _cornerRadius / 2)); + -dy - (canAdjustTooltipNose ? 0 : cornerRadius / 2)); // ___ // / - path.lineTo(rightLineWidth - _cornerRadius, -dy); + path.lineTo(rightLineWidth - cornerRadius, -dy); // ___| // / path.quadraticBezierTo( - rightLineWidth, -dy, rightLineWidth, -dy - _cornerRadius); - path.lineTo(rightLineWidth, -dy - tooltipHeight + _cornerRadius); + rightLineWidth, -dy, rightLineWidth, -dy - cornerRadius); + path.lineTo(rightLineWidth, -dy - tooltipHeight + cornerRadius); // _______ // ___| // / path.quadraticBezierTo(rightLineWidth, -dy - tooltipHeight, - rightLineWidth - _cornerRadius, -dy - tooltipHeight); - path.lineTo(-leftLineWidth + _cornerRadius, -dy - tooltipHeight); + rightLineWidth - cornerRadius, -dy - tooltipHeight); + path.lineTo(-leftLineWidth + cornerRadius, -dy - tooltipHeight); // _______ // | ___| // / path.quadraticBezierTo(-leftLineWidth, -dy - tooltipHeight, -leftLineWidth, - -dy - tooltipHeight + _cornerRadius); - path.lineTo(-leftLineWidth, -dy - _cornerRadius); + -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); } // ________ @@ -854,10 +884,10 @@ class SfRectangularTooltipShape extends SfTooltipShape { Paint paint, Animation animation, Rect trackRect}) { - final double leftPadding = _tooltipTextPadding.dx / 2; + final double leftPadding = tooltipTextPadding.dx / 2; final double minLeftX = trackRect.left; final Path path = _updateRectangularTooltipWidth( - textPainter.size + _tooltipTextPadding, + textPainter.size + tooltipTextPadding, offset.dy, trackRect, thumbCenter.dx); @@ -898,8 +928,8 @@ class SfRectangularTooltipShape extends SfTooltipShape { trackRect.left - thumbCenter.dx; final double dy = offset.dy + - _tooltipTriangleHeight + - (pathRect.size.height - _tooltipTriangleHeight) / 2 + + tooltipTriangleHeight + + (pathRect.size.height - tooltipTriangleHeight) / 2 + textPainter.height / 2; textPainter.paint(context.canvas, Offset(dx, -dy)); context.canvas.restore(); diff --git a/packages/syncfusion_flutter_xlsio/CHANGELOG.md b/packages/syncfusion_flutter_xlsio/CHANGELOG.md index 2c8065c94..bedf097bc 100644 --- a/packages/syncfusion_flutter_xlsio/CHANGELOG.md +++ b/packages/syncfusion_flutter_xlsio/CHANGELOG.md @@ -1,3 +1,21 @@ +## [Unreleased] + +**Features** + +* Provided support to add hyperlinks to texts and images. +* Provided support to insert or delete rows and columns. +* Provided support to autofit rows and columns. +* Provided support to add logical functions, string functions, and nested formulas. +* Provided support to protect workbooks and worksheets. + +## [18.3.40] - 10/13/2020 + +**API Changes** +* Changed the property name for Worksheet showGridLines to showGridlines. +* Changed the method name for Workbook class saveStream to saveAsStream. +* Removed the save method from Workbook class. +* Removed the addFile method from PicturesCollection class. + ## [18.3.35-beta.1] - 10/02/2020 **Features** diff --git a/packages/syncfusion_flutter_xlsio/README.md b/packages/syncfusion_flutter_xlsio/README.md index 24a50f652..53c795028 100644 --- a/packages/syncfusion_flutter_xlsio/README.md +++ b/packages/syncfusion_flutter_xlsio/README.md @@ -26,6 +26,9 @@ The Excel package is a non-UI and reusable Flutter library to create Excel docum - [Apply formatting](#apply-formatting) - [Add images](#add-images) - [Add charts](#add-charts) + - [Add hyperlinks](#add-hyperlinks) + - [Manipulate rows and Columns](#manipulate-rows-and-columns) + - [Protect workbook and worksheets](#Protect-workbook-and-worksheets) - [Support and feedback](#support-and-feedback) - [About Syncfusion](#about-syncfusion) @@ -38,6 +41,9 @@ The following are the key features of Syncfusion Flutter XlsIO. * Add formulas to Excel worksheet cells * Add images to Excel worksheet * Add charts to Excel worksheet +* Add hyperlinks to Excel worksheet +* Manipulate rows and columns of Excel worksheet +* Add protection to Excel document. ## Get the demo application @@ -82,7 +88,8 @@ final Workbook workbook = new Workbook(); //Accessing worksheet via index. workbook.worksheets[0]; // Save the document. -workbook.save('CreateExcel.xlsx'); +List bytes = workbook.saveAsStream(); +File('CreateExcel.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); @@ -104,7 +111,8 @@ sheet.getRangeByName('A3').setNumber(44); //Add DateTime sheet.getRangeByName('A5').setDateTime(DateTime(2020,12,12,1,10,20)); // Save the document. -workbook.save('AddingTextNumberDateTime.xlsx'); +List bytes = workbook.saveAsStream(); +File('AddingTextNumberDateTime.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); @@ -130,7 +138,8 @@ sheet.enableSheetCalculations(); sheet.getRangeByName('A3').setFormula('=A1+A2'); // Save the document. -workbook.save('AddingFormula.xlsx'); +List bytes = workbook.saveAsStream(); +File('AddingFormula.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); @@ -170,7 +179,8 @@ globalStyle.setNumberFormat = '_(\$* #,##0_)';; //Apply GlobalStyle sheet.getRangeByName('A1').cellStyle = globalStyle; // Save the document. -workbook.save('ApplyGlobalStyle .xlsx'); +List bytes = workbook.saveAsStream(); +File('ApplyGlobalStyle.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); @@ -190,7 +200,8 @@ final Worksheet sheet = workbook.worksheets[0]; sheet.getRangeByName('A1').builtInStyle = BuiltInStyles.linkedCell; // Save the document. -workbook.save('ApplyBuildInStyle.xlsx'); +List bytes = workbook.saveAsStream(); +File('ApplyBuildInStyle.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); @@ -212,7 +223,8 @@ range.setNumber(100); range.numberFormat = '\S#,##0.00'; // Save the document. -workbook.save('ApplyNumberFormat .xlsx'); +List bytes = workbook.saveAsStream(); +File('ApplyNumberFormat.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); @@ -230,10 +242,12 @@ final Workbook workbook = new Workbook(); final Worksheet sheet = workbook.worksheets[0]; //Adding a picture -final Picture picture = sheet.pictures.addFile(1, 1, 'images.png'); +final List bytes = File('image.png').readAsBytesSync(); +final Picture picture = sheet.picutes.addStream(1, 1, bytes); // Save the document. -workbook.save('AddingImage.xlsx'); +List bytes = workbook.saveAsStream(); +File('AddingImage.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); @@ -279,12 +293,220 @@ chart.chartType = ExcelChartType.column; chart.dataRange = sheet.getRangeByName('A1:B4'); // Save the document. -workbook.save('CreateExcel.xlsx'); +List bytes = workbook.saveAsStream(); +File('ExcelCharts.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); ``` +### Add hyperlinks + +Use the following code to add hyperlinks to Excel worksheet. + +```dart +// Create a new Excel Document. +final Workbook workbook = Workbook(); + +// Accessing sheet via index. +final Worksheet sheet = workbook.worksheets[0]; + +//Creating a Hyperlink for a Website. +final Hyperlink hyperlink = sheet.hyperlinks.add(sheet.getRangeByName('A1'), + HyperlinkType.url, 'https://www.syncfusion.com'); +hyperlink.screenTip = + 'To know more about Syncfusion products, go through this link.'; +hyperlink.textToDisplay = 'Syncfusion'; + +// Save and dispose workbook. +final List bytes = workbook.saveAsStream(); +File('Hyperlinks.xlsx').writeAsBytes(bytes); +workbook.dispose(); + +``` + +### Manipulate rows and Columns + +This section covers how rows and columns are manipulated in Excel Worksheets. + +**Apply Autofits** + +Use the following code to apply autofits to single cells of the Excel worksheet. + +```dart +// Create a new Excel Document. +final Workbook workbook = Workbook(); + +// Accessing sheet via index. +final Worksheet sheet = workbook.worksheets[0]; + +final Range range = sheet.getRangeByName('A1'); +range.setText('WrapTextWrapTextWrapTextWrapText'); +range.cellStyle.wrapText = true; + +final Range range1 = sheet.getRangeByName('B1'); +range1.setText('This is long text'); + +// AutoFit applied to a single row +sheet.autoFitRow(1); + +// AutoFit applied to a single Column. +sheet.autoFitColumn(2); + +// Save and dispose workbook. +final List bytes = workbook.saveAsStream(); +File('AutoFit.xlsx').writeAsBytes(bytes); +workbook.dispose(); + +``` + +Use the following code to apply autofits to multiple cells of the Excel worksheet. + +```dart +// Create a new Excel Document. +final Workbook workbook = Workbook(); + +// Accessing sheet via index. +final Worksheet sheet = workbook.worksheets[0]; + +// Assigning text to cells +final Range range = sheet.getRangeByName('A1:D1'); +range.setText('This is Long Text'); +final Range range1 = sheet.getRangeByName('A2:A5'); +range1.setText('This is Long Text using AutoFit Columns and Rows'); +range1.cellStyle.wrapText = true; + +// Auto-Fit column the range +range.autoFitColumns(); + +// Auto-Fit row the range +range1.autoFitRows(); + +// Save and dispose workbook. +final List bytes = workbook.saveAsStream(); +File('AutoFits.xlsx').writeAsBytes(bytes); +workbook.dispose(); + +``` +**Insert/Delete Rows and Colums** + +Use the following code to insert rows and columns to the Excel worksheet. + +```dart +// Create a new Excel Document. +final Workbook workbook = Workbook(); + +// Accessing sheet via index. +final Worksheet sheet = workbook.worksheets[0]; + +Range range = sheet.getRangeByName('A1'); +range.setText('Hello'); + +range = sheet.getRangeByName('B1'); +range.setText('World'); + +// Insert a row +sheet.insertRow(1, 1, ExcelInsertOptions.formatAsAfter); + +// Insert a column. +sheet.insertColumn(2, 1, ExcelInsertOptions.formatAsBefore); + +// Save and dispose workbook. +final List bytes = workbook.saveAsStream(); +File('InsertRowandColumn.xlsx').writeAsBytes(bytes); +workbook.dispose(); + +``` + +Use the following code to delete rows and columns of Excel worksheet. + +```dart +// Create a new Excel Document. +final Workbook workbook = Workbook(); + +// Accessing sheet via index. +final Worksheet sheet = workbook.worksheets[0]; + +Range range = sheet.getRangeByName('A2'); +range.setText('Hello'); + +range = sheet.getRangeByName('C2'); +range.setText('World'); + +// Delete a row +sheet.deleteRow(1, 1); + +// Delete a column. +sheet.deleteColumn(2, 1); + +// Save and dispose workbook. +final List bytes = workbook.saveAsStream(); +File('DeleteRowandColumn.xlsx').writeAsBytes(bytes); +workbook.dispose(); + +``` + +### Protect workbook and worksheets + +This section covers the various protection options in the Excel document. + +**Protect Workbook** + +Use the following code to protect workbook of Excel document. + +```dart +// Create a new Excel Document. +final Workbook workbook = Workbook(); + +// Accessing sheet via index. +final Worksheet sheet = workbook.worksheets[0]; + +// Assigning text to cells +final Range range = sheet.getRangeByName('A1'); +range.setText('WorkBook Protected'); + +final bool isProtectWindow = true; +final bool isProtectContent = true; + +// Protect Workbook +workbook.protect(isProtectWindow, isProtectContent, 'password'); + +// Save and dispose workbook. +final List bytes = workbook.saveAsStream(); +File('WorkbookProtect.xlsx').writeAsBytes(bytes); +workbook.dispose(); + +``` + +**Protect Worksheets** + +Use the following code to protect worksheets in the Excel document. + +```dart +// Create a new Excel Document. +final Workbook workbook = Workbook(); + +// Accessing sheet via index. +final Worksheet sheet = workbook.worksheets[0]; + +// Assigning text to cells +final Range range = sheet.getRangeByName('A1'); +range.setText('Worksheet Protected'); + +// ExcelSheetProtectionOption +final ExcelSheetProtectionOption options = ExcelSheetProtectionOption(); +options.all = true; + +// Protecting the Worksheet by using a Password +sheet.protect('Password', options); + +// Save and dispose workbook. +final List bytes = workbook.saveAsStream(); +File('WorksheetProtect.xlsx').writeAsBytes(bytes); +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). diff --git a/packages/syncfusion_flutter_xlsio/example/lib/main.dart b/packages/syncfusion_flutter_xlsio/example/lib/main.dart index 0efb334e1..2abbc4470 100644 --- a/packages/syncfusion_flutter_xlsio/example/lib/main.dart +++ b/packages/syncfusion_flutter_xlsio/example/lib/main.dart @@ -61,7 +61,7 @@ class _CreateExcelState extends State { final Workbook workbook = Workbook(); //Accessing via index final Worksheet sheet = workbook.worksheets[0]; - sheet.showGridLines = false; + sheet.showGridlines = false; // Enable calculation for worksheet. sheet.enableSheetCalculations(); @@ -214,7 +214,7 @@ class _CreateExcelState extends State { range9.cellStyle.vAlign = VAlignType.center; //Save and launch the excel. - final List bytes = workbook.saveStream(); + final List bytes = workbook.saveAsStream(); //Dispose the document. workbook.dispose(); diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/calc_engine.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/calc_engine.dart index c79d67c21..2ebdb9750 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/calc_engine.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/calc_engine.dart @@ -116,12 +116,14 @@ class CalcEngine { final _expressionCannotEndWithAnOperator = 5; final _invalidCharactersFollowingAnOperator = 6; final _mismatchedParentheses = 8; + final _requiresASingleArgument = 10; final _requires3Args = 11; final _badIndex = 14; final _tooComplex = 15; final _missingFormula = 17; final _improperFormula = 18; final _cellEmpty = 20; + final _badFormula = 21; final _emptyExpression = 22; final _mismatchedTics = 24; final _wrongNumberArguments = 25; @@ -132,6 +134,7 @@ class CalcEngine { final _checkDanglingStack = false; bool _isRangeOperand = false; bool _multiTick = false; + bool _isInteriorFunction = false; double _dateTime1900Double; final _dateTime1900 = DateTime(1900, 1, 1, 0, 0, 0); @@ -169,11 +172,15 @@ class CalcEngine { final String _tempSheetPlaceHolder = String.fromCharCode(133); final int _columnMaxCount = -1; + // ignore: unused_field + int _hitCount = 0; Map _libraryFunctions; final String _validFunctionNameChars = '_'; final bool _getValueFromArgPreserveLeadingZeros = false; bool _ignoreCellValue = false; bool _findNamedRange = false; + // ignore: unused_field + bool _exteriorFormula = false; bool _isIndexInteriorFormula = false; bool _isErrorString = false; @@ -366,6 +373,17 @@ class CalcEngine { _addFunction('Min', '_computeMin'); _addFunction('Count', '_computeCount'); _addFunction('If', '_computeIf'); + _addFunction('Index', '_computeIndex'); + _addFunction('Match', '_computeMatch'); + _addFunction('And', '_computeAnd'); + _addFunction('Or', '_computeOr'); + _addFunction('Not', '_computeNot'); + _addFunction('Today', '_computeToday'); + _addFunction('Now', '_computeNow'); + _addFunction('Trim', '_computeTrim'); + _addFunction('Concatenate', '_computeConcatenate'); + _addFunction('Upper', '_computeUpper'); + _addFunction('Lower', '_computeLower'); } /// A method that increases the calculation level of the CalcEngine. @@ -593,6 +611,7 @@ class CalcEngine { if (_errorStrings.contains(s)) return s; _stack._push(_getValueFromParentObject(s, true)); } else if (formula[i] == 'q') { + formula = _computeInteriorFunctions(formula); final int ii = formula.substring(i + 1).indexOf(_leftBracket); if (ii > 0) { int bracketCount = 0; @@ -1097,7 +1116,7 @@ class CalcEngine { _isErrorString = false; return _errorStrings[x].toString(); } - _stack._push(math.pow(d1, d).toString()); + _stack._push(pow(d1, d).toString()); i = i + 1; } @@ -1190,6 +1209,39 @@ class CalcEngine { case '_computeIf': return _computeIf(args); break; + case '_computeIndex': + return _computeIndex(args); + break; + case '_computeMatch': + return _computeMatch(args); + break; + case '_computeAnd': + return _computeAnd(args); + break; + case '_computeOr': + return _computeOr(args); + break; + case '_computeNot': + return _computeNot(args); + break; + case '_computeToday': + return _computeToday(args); + break; + case '_computeNow': + return _computeNow(args); + break; + case '_computeTrim': + return _computeTrim(args); + break; + case '_computeConcatenate': + return _computeConcatenate(args); + break; + case '_computeUpper': + return _computeUpper(args); + break; + case '_computeLower': + return _computeLower(args); + break; default: return args; break; @@ -1208,9 +1260,7 @@ class CalcEngine { } for (final r in ranges) { adjustRange = r; - ////is a cellrange - // ignore: prefer_contains - if (adjustRange.indexOf(':') > -1 && _isRange(adjustRange)) { + if (adjustRange.contains(':') && _isRange(adjustRange)) { if (r.startsWith(_tic)) { return _errorStrings[1].toString(); } @@ -1322,7 +1372,7 @@ class CalcEngine { /// Returns the maximum value of all values listed in the argument. String _computeMax(String range) { - double max = -double.maxFinite; + double maxValue = -double.maxFinite; double d; String s1; final List ranges = _splitArgsPreservingQuotedCommas(range); @@ -1353,7 +1403,7 @@ class CalcEngine { if (s1.isNotEmpty) { d = double.tryParse(s1.replaceAll(_tic, '')); if (d != null) { - max = math.max(max, d); + maxValue = max(maxValue, d); } else if (_errorStrings.contains(s1)) { return s1; } @@ -1370,7 +1420,7 @@ class CalcEngine { if (s1.isNotEmpty) { d = double.tryParse(s1.replaceAll(_tic, '')); if (d != null) { - max = math.max(max, d); + maxValue = max(maxValue, d); } else { if (s1.startsWith(_tic)) { return _errorStrings[1].toString(); @@ -1379,8 +1429,8 @@ class CalcEngine { } } } - if (max != -double.maxFinite) { - return max.toString(); + if (maxValue != -double.maxFinite) { + return maxValue.toString(); } return '0'; @@ -1388,7 +1438,7 @@ class CalcEngine { /// Returns the minimum value of all values listed in the argument. String _computeMin(String range) { - double min = double.maxFinite; + double minValue = double.maxFinite; double d; String s1; final List ranges = _splitArgsPreservingQuotedCommas(range); @@ -1422,7 +1472,7 @@ class CalcEngine { if (s1.isNotEmpty) { double.tryParse(s1.replaceAll(_tic, '')); if (d != null) { - min = math.min(min, d); + minValue = min(minValue, d); } } } @@ -1437,7 +1487,7 @@ class CalcEngine { if (s1.isNotEmpty) { d = double.tryParse(s1.replaceAll(_tic, '')); if (d != null) { - min = math.min(min, d); + minValue = min(minValue, d); } else { if (s1.startsWith(_tic)) { return _errorStrings[1].ToString(); @@ -1446,8 +1496,8 @@ class CalcEngine { } } } - if (min != double.maxFinite) { - return min.toString(); + if (minValue != double.maxFinite) { + return minValue.toString(); } return '0'; @@ -1620,6 +1670,432 @@ class CalcEngine { return s1; } + /// Returns the value at a specified row and column from within a given range. + String _computeIndex(String arg) { + String result; + if (arg == null || arg == '') { + return _formulaErrorStrings[_invalidArguments]; + } + final List args = _splitArgsPreservingQuotedCommas(arg); + final int argCount = args.length; + if (argCount < 2 || args.isEmpty) { + return _formulaErrorStrings[_wrongNumberArguments]; + } + for (int index = 1; index < argCount; index++) { + //To check third argument since it is optional + if (index <= (argCount - 1)) { + //To Check #NAME?,#VALUE! error string for argument 2,3 since it is numeric type + final String checkString2 = _getValueFromArg(args[1]); + if (_errorStrings.contains(checkString2)) { + return checkString2; + } + } + } + String r = args[0]; + r = r.replaceAll(_tic, ' '); + int i = r.indexOf(':'); + ////Single Cell + if (i == -1) { + if (_isCellReference(r)) { + r = r + ':' + r; + } + } + final String sheet = _getSheetToken(r); + i = r.indexOf(':'); + int row = (argCount == 1) + ? 1 + : double.tryParse(_getValueFromArg(args[1])).toInt(); + int col = (argCount <= 2) + ? 1 + : double.tryParse(_getValueFromArg(args[2])).toInt(); + int top = _getRowIndex(r.substring(0, i)); + int bottom = _getRowIndex(r.substring(i + 1)); + if (!(top != -1 || bottom == -1) == (top == -1 || bottom != -1)) { + return _errorStrings[5].toString(); + } + if (top == -1 && _grid is Worksheet) { + top = (_grid).getFirstRow(); + } + if (bottom == -1 && _grid is Worksheet) { + bottom = (_grid).getLastRow(); + } + int left = _getColIndex(r.substring(0, i)); + int right = _getColIndex(r.substring(i + 1)); + if (left == -1 && _grid is Worksheet) { + left = (_grid).getFirstColumn(); + } + if (right == -1 && _grid is Worksheet) { + right = (_grid).getLastColumn(); + } + if (argCount == 2 && row > bottom - top + 1) { + col = row; + row = 1; + } + if (row > bottom - top + 1 || col > right - left + 1) { + return _errorStrings[2].toString(); + } + + row = _getRowIndex(r.substring(0, i)) + (row <= 0 ? row : row - 1); + if (_getRowIndex(r.substring(0, i)) == -1 && _grid is Worksheet) { + row = (_grid).getFirstRow(); + } + col = _getColIndex(r.substring(0, i)) + (col <= 0 ? col : col - 1); + if (_getColIndex(r.substring(0, i)) == -1 && _grid is Worksheet) { + col = (_grid).getFirstColumn(); + } + + result = _getValueFromArg( + sheet + RangeInfo._getAlphaLabel(col) + row.toString()); + if (!_isIndexInteriorFormula && result.isEmpty) { + return '0'; + } + return result; + } + + /// Finds the index a specified value in a lookup_range. + String _computeMatch(String arg) { + if (arg == null || arg == '') { + return _formulaErrorStrings[_invalidArguments]; + } + final List args = _splitArgsPreservingQuotedCommas(arg); + final int argCount = args.length; + if (argCount != 3 && argCount != 2 || arg.isEmpty) { + return _formulaErrorStrings[_wrongNumberArguments]; + } + // To Check the error String + final String checkString = _getValueFromArg(args[0]); + if (_errorStrings.contains(checkString)) { + return checkString; + } + int m = 1; + List cells = []; + final String r = args[1].replaceAll(_tic, ' '); + final int i = r.indexOf(':'); + if (argCount == 3) { + // To Check the error String + final String checkString = _getValueFromArg(args[2]); + if (_errorStrings.contains(checkString)) { + return checkString; + } + String thirdArg = _getValueFromArg(args[2]); + thirdArg = thirdArg.replaceAll(_tic, ' '); + m = double.tryParse(thirdArg).toInt(); + if (thirdArg.contains(_tic) && + (thirdArg.contains(_trueValueStr) || + thirdArg.contains(_falseValueStr))) { + return _errorStrings[1].toString(); + } else if (thirdArg == _falseValueStr) { + m = 0; + } else if (thirdArg == _trueValueStr) { + m = 1; + } + } + final String searchItem = + _getValueFromArg(args[0]).replaceAll(_tic, ' ').toUpperCase(); + if (searchItem == null || searchItem == '') { + return _errorStrings[5].toString(); + } + if (i > -1) { + int row1 = _getRowIndex(r.substring(0, i + 1)); + int row2 = _getRowIndex(r.substring(0, i + 1)); + int col1 = _getColIndex(r.substring(0, i + 1)); + int col2 = _getColIndex(r.substring(0, i + 1)); + if (_grid is Worksheet) { + if (!(row1 != -1 || row2 == -1) == (row1 == -1 || row2 != -1)) { + return _errorStrings[5].toString(); + } + if (row1 == -1) { + row1 = (_grid).getFirstRow(); + } + if (col1 == -1) { + col1 = (_grid).getFirstColumn(); + } + if (row2 == -1) { + row2 = (_grid).getLastRow(); + } + if (col2 == -1) { + col2 = (_grid).getLastColumn(); + } + } + } + if (cells == null || (cells != null && cells.isEmpty)) { + cells = _getCellsFromArgs(_stripTics(r)); + } + int index = 1; + int emptyValueIndex = 0; + String oldValue; + String newValue; + for (final String s in cells) { + if (_isCellReference(s.replaceAll(_tic, ' '))) { + newValue = _getValueFromArg(s).replaceAll(_tic, ' ').toUpperCase(); + } else { + newValue = s.replaceAll(_tic, '').toUpperCase(); + } + if (oldValue != null) { + if (m == 1) { + if (_matchCompare(newValue, oldValue) < 0 && newValue == searchItem) { + index--; + break; + } else if (m == -1) { + if (_matchCompare(newValue, oldValue) > 0) { + index = -1; + break; + } + } + } + } + if ((m == 0 || m == 1) && _matchCompare(searchItem, newValue) == 0) { + break; + } else if (m == 1 && _matchCompare(searchItem, newValue) < 0) { + index--; + break; + } else if (m == -1 && _matchCompare(searchItem, newValue) > 0) { + index--; + break; + } + if (m == 1 && newValue == null) { + emptyValueIndex++; + } else { + index++; + } + if (oldValue == null && newValue != null) { + index = index + emptyValueIndex; + emptyValueIndex = 0; + } + oldValue = newValue; + } + if (index > 0 && index <= cells.length) { + return index.toString(); + } else { + return '#N/A'; + } + } + + /// Removes outer quote marks from a string with no inner quote marks. + String _stripTics(String s) { + if (s.length > 1 && s[0] == _tic[0] && s[s.length - 1] == _tic[0]) { + if (s.substring(1, s.length - 2).contains(_tic)) { + s = s.substring(1, s.length - 2); + } else if (_multiTick) { + s = s.substring(1, s.length - 2); + } + } + return s; + } + + int _matchCompare(Object o1, Object o2) { + final String s1 = o1.toString(); + final String s2 = o2.toString(); + final double d1 = double.tryParse(s1); + final double d2 = double.tryParse(s2); + if (s1.contains('.') || s2.contains('.')) { + return d1.compareTo(d2); + } else { + return s1.compareTo(s2); + } + } + + /// Returns the And of all values treated as logical values listed in the argument. + String _computeAnd(String range) { + bool sum = true; + if (range == null || range.isEmpty) { + return _formulaErrorStrings[_wrongNumberArguments].toString(); + } + String s1; + double d; + final List ranges = _splitArgsPreservingQuotedCommas(range); + for (final String r in ranges) { + if (_splitArguments(r, ':').length > 1 && + _isCellReference(r.replaceAll(_tic, ''))) { + for (final String s in _getCellsFromArgs(r)) { + try { + s1 = _getValueFromArg(s); + if (_errorStrings.contains(s1)) { + return s1; + } + } catch (e) { + _exceptionThrown = true; + return _errorStrings[4].ToString(); + } + d = double.tryParse(s1); + sum &= s1 == '' + ? _trueValueStr.toLowerCase() == 'true' + : ((s1 == _trueValueStr) || d != null && d != 0); + if (!sum) { + return _falseValueStr; + } + } + } else { + try { + s1 = _getValueFromArg(r); + if (s1.startsWith(_tic) && + (r.replaceAll(_tic, '').toLowerCase() != 'true')) { + return _errorStrings[1].toString(); + } + if (ranges.length == 1) { + if (s1 == null && s1.isEmpty) { + return _errorStrings[1].toString(); + } + } + if (_errorStrings.contains(s1)) { + return s1; + } + + if (DateTime.tryParse(s1) != null) { + return _trueValueStr; + } else if ((double.tryParse(s1) == null) && + s1 != '' && + !(s1.replaceAll(_tic, '').toLowerCase() == 'true' || + s1.replaceAll(_tic, '').toLowerCase() == 'false')) { + return (_isCellReference(r)) + ? _errorStrings[1].toString() + : _errorStrings[5].toString(); + } + } catch (e) { + _exceptionThrown = true; + return _errorStrings[4].toString(); + } + d = double.tryParse(s1); + sum &= ((s1.replaceAll(_tic, '').toLowerCase() == 'true') || + d != null && d != 0); + if (!sum) { + return _falseValueStr; + } + } + } + return sum ? _trueValueStr : _falseValueStr; + } + + /// Returns the And of all values treated as logical values listed in the argument. + String _computeOr(String range) { + bool sum = false; + if (range == null || range.isEmpty) { + return _formulaErrorStrings[_wrongNumberArguments].toString(); + } + String s1; + double d; + final List ranges = _splitArgsPreservingQuotedCommas(range); + for (final String r in ranges) { + if (_splitArguments(r, ':').length > 1 && + _isCellReference(r.replaceAll(_tic, ''))) { + for (final String s in _getCellsFromArgs(r)) { + try { + s1 = _getValueFromArg(s); + if (_errorStrings.contains(s1)) { + return s1; + } + } catch (e) { + _exceptionThrown = true; + return _errorStrings[4].ToString(); + } + d = double.tryParse(s1); + sum &= s1 == '' + ? _trueValueStr.toLowerCase() == 'true' + : ((s1 == _trueValueStr) || d != null && d != 0); + if (!sum) { + return _falseValueStr; + } + } + } else { + try { + s1 = _getValueFromArg(r); + if (s1.startsWith(_tic) && + (r.replaceAll(_tic, '').toLowerCase() != 'true')) { + return _errorStrings[1].toString(); + } + if (ranges.length == 1) { + if (s1 == null && s1.isEmpty) { + return _errorStrings[1].toString(); + } + } + if (_errorStrings.contains(s1)) { + return s1; + } + + if (DateTime.tryParse(s1) != null) { + return _trueValueStr; + } else if ((double.tryParse(s1) == null) && + s1 != '' && + !(s1.replaceAll(_tic, '').toLowerCase() == 'true' || + s1.replaceAll(_tic, '').toLowerCase() == 'false')) { + return (_isCellReference(r)) + ? _errorStrings[1].toString() + : _errorStrings[5].toString(); + } + } catch (e) { + _exceptionThrown = true; + return _errorStrings[4].toString(); + } + d = double.tryParse(s1); + sum |= ((s1.replaceAll(_tic, '').toLowerCase() == 'true') || + d != null && d != 0); + if (sum) { + return _trueValueStr; + } + } + } + return sum ? _trueValueStr : _falseValueStr; + } + + /// Flips the logical value represented by the argument. + String _computeNot(String args) { + double d1; + String s = args; + ////parsed formula + if (args.isNotEmpty && + !_isLetter(args.codeUnitAt(0)) && + _indexOfAny(args, [parseArgumentSeparator, ':']) > -1) { + return _formulaErrorStrings[_requiresASingleArgument]; + } else { + try { + s = _getValueFromArg(s); + d1 = double.tryParse(s); + if (s == (_trueValueStr)) { + s = _falseValueStr; + } else if (s == (_falseValueStr)) { + s = _trueValueStr; + } else if (d1 != null) { + ////Flip the value. + if (d1.abs() > 1e-10) { + s = _falseValueStr; + } else { + s = _trueValueStr; + } + } + } catch (e) { + _exceptionThrown = true; + return e.toString(); + } + } + return s; + } + + /// A method to split the arguments using argument seperator. + List _splitArguments(String args, String argSeperator) { + final List splitArg = []; + int start = 0; + int ticCount = 0; + for (int i = 0; i < args.length; i++) { + if (args[i] == '"') { + if (ticCount == 0) { + ticCount++; + } else { + ticCount = 0; + } + } + if ((args[i] == (argSeperator) && ticCount != 1)) { + splitArg.add(args.substring(start, i - start)); + start = i + 1; + } + if (i == (args.length - 1)) { + splitArg.add(args.substring(start, i - start + 1)); + } + } + + final List argList = splitArg; + return argList; + } + /// A Virtual method to compute the value based on the argument passed in. String _getValueFromArg(String arg) { if (_textIsEmpty(arg)) { @@ -1629,6 +2105,24 @@ class CalcEngine { arg = arg.replaceAll('u', '-'); arg = arg.replaceAll('~', _tic + _tic); + if (!_isUpper(arg[0]) && + (_isDigit(arg[0].codeUnitAt(0)) || + arg[0] == parseDecimalSeparator || + arg[0] == '+' || + arg[0] == '-' || + arg[0] == 'n' || + (arg.length == 1 && (arg[0] == 'i' || arg[0] == 'j')))) { + if (arg[0] == 'n') { + arg = arg.substring(1); + } + d = double.tryParse(arg); + if (d != null) { + return _getValueFromArgPreserveLeadingZeros ? arg : d.toString(); + } else if (arg.startsWith(_trueValueStr) || + arg.startsWith(_falseValueStr)) { + return arg; + } + } if (_ignoreCellValue && !(arg.startsWith(_trueValueStr) || arg.startsWith(_falseValueStr))) { _ignoreCellValue = false; @@ -1767,6 +2261,17 @@ class CalcEngine { ////check right side j = i + 1; + if (range[j] == _sheetToken) { + j++; + while (j < range.length && range[j] != _sheetToken) { + j++; + } + + if (j < range.length) { + j++; + } + } + ////handle possible sheetnames if (j < range.length - 6 && range[j] == _charTIC) { j = range.indexOf(_sheetToken, j + 1); @@ -2256,6 +2761,26 @@ class CalcEngine { } left = text.substring(j, j + leftErrorIndex + 1); leftIndex = j; + } else if (text[j] == _rightBracket) { + ////Library member. + int bracketCount = 0; + int k = j - 1; + while (k > 0 && (text[k] != 'q' || bracketCount != 0)) { + if (text[k] == 'q') { + bracketCount--; + } else if (text[k] == _rightBracket) { + bracketCount++; + } + + k--; + } + + if (k < 0) { + throw Exception(_formulaErrorStrings[_badLibrary]); + } + + left = text.substring(k, j + 1); + leftIndex = k; } else if (!_isDigit(text.codeUnitAt(j)) && text[j] != '%' && (!text.contains(':') || @@ -2997,12 +3522,12 @@ class CalcEngine { maxCol = maxRow = _intMinValue; for (final tempArgs in argList) { d = _getRowIndex(tempArgs); - minRow = math.min(minRow, d); - maxRow = math.max(maxRow, d); + minRow = min(minRow, d); + maxRow = max(maxRow, d); d = _getColIndex(tempArgs); - minCol = math.min(minCol, d); - maxCol = math.max(maxCol, d); + minCol = min(minCol, d); + maxCol = max(maxCol, d); } row1 = minRow; row2 = maxRow; @@ -3368,4 +3893,375 @@ class CalcEngine { } return text; } + + String _computeInteriorFunctions(String formula) { + try { + if (_textIsEmpty(formula)) { + return formula; + } + + ////int q = formula.LastIndexOf('q'); + int q = _findLastqNotInBrackets(formula); + while (q > 0) { + final int last = formula.substring(q).indexOf(_rightBracket); + if (last == -1) { + return _formulaErrorStrings[_badFormula]; + } + + String s = formula.substring(q, q + last + 1); + + // To check if the function contains CELL formula embedded with other formulas like INDEX,IF... + final int q1 = _findLastqNotInBrackets(formula.substring(0, q)); + final String s1 = q1 >= 0 + ? formula.substring( + q1, q1 + formula.substring(q1).indexOf(_rightBracket)) + : ''; + + // Below code has been added to check whether the Value formula is interior of SUMPRODUCT + if ((s.contains('qVALUE') || + s.contains('qINT') || + s.contains('qROW')) && + s1.contains('SUMPRODUCT')) _exteriorFormula = true; + + if (s1.contains('qINDEX') && + (s1.contains('qCELL') || + s1.contains('qCOUNT') || + s1.contains('qOFFSET'))) { + _isIndexInteriorFormula = true; + // Below code has been added to calculate when the index is embedded formula and the index array condtains index formula. + if (s.startsWith('qINDEX')) { + _hitCount = _computedValueLevel + 1; + } + } + + s = _computedValue(s); + if (s != null && + s.isNotEmpty && + s[0] == _tic[0] && + s[s.length - 1] == _tic[0]) { + String newS = s.substring(1, 1 + s.length - 2); + if (newS.contains(_tic)) { + _multiTick = true; + newS = newS.replaceAll(_tic, '|'); + } + s = _tic + newS + _tic; + } + if (!_isInteriorFunction) s = _markupResultToIncludeInFormula(s); + + _isInteriorFunction = false; + formula = formula.substring(0, q) + s + formula.substring(q + last + 1); + q = _findLastqNotInBrackets(formula); + } + } catch (e) { + _exceptionThrown = true; + return e.toString(); + } + + return formula; + } + + int _findLastqNotInBrackets(String s) { + int found = -1; + bool lastBracket = false; + int i = s.length - 1; + while (i > -1) { + if (s[i] == 'q' && lastBracket) { + found = i; + break; + } + + if (s[i] == _leftBracket) { + lastBracket = true; + } else if (s[i] == _rightBracket) { + lastBracket = false; + } + + i--; + } + + return found; + } + + String _markupResultToIncludeInFormula(String s) { + if (s.isNotEmpty && s[0] == '-' && double.tryParse(s) != null) { + s = 'nu' + s.substring(1); + } else if (s.isNotEmpty && + (s[0] == _tic[0] || s[0] == _bMarker || s[0] == '#')) { + ////Pass on the String... + } else if (s.startsWith('TRUE') || s.startsWith('FALSE')) { + ////Pass on the bool... + } else { + if (double.tryParse(s) != null) { + s = s.replaceAll(parseArgumentSeparator, String.fromCharCode(32)); + + s = 'n' + s; + } else { + //WPF-37458- To pass the computed result of interior functions in single cell array formula + if (!_isRange(s) && + s.startsWith(_braceLeft) && + s.endsWith(_braceRight)) { + s = s.replaceAll('{', '').replaceAll('}', ''); + String strValue = ''; + final List ranges = _splitArgsPreservingQuotedCommas(s); + for (final String r in ranges) { + if (double.tryParse(r) != null) { + strValue += 'n' + r + parseArgumentSeparator; + } + } + s = strValue.substring(0, strValue.length - 2); + } else if (!_isRange(s)) { + s = _tic + s + _tic; + } + } + } + return s; + } + + /// Removes all leading and trailing white-space characters. + String _computeTrim(String args) { + String s = _getValueFromArg(args).trim(); + int len = 0; + + ////strip out interior double, triple, etc spaces... + while (s.length != len) { + len = s.length; + s = s.replaceAll(' ', ' '); + } + return s; + } + + /// Returns the current date and time as a date serial number. + String _computeNow(String argList) { + if (argList != null && argList.isNotEmpty) { + return _formulaErrorStrings[_wrongNumberArguments]; + } + final DateTime dt = DateTime.now(); + if (excelLikeComputations) { + return dt.toString(); + } + return Range._toOADate(dt).toString(); + } + + /// Returns the current date as a date serial number. + String _computeToday(String argList) { + if (argList != null && argList.isNotEmpty) { + return _formulaErrorStrings[_wrongNumberArguments]; + } + final DateTime dt = DateTime.now(); + + if (excelLikeComputations) { + final DateTime result = DateTime.tryParse(dt.year.toString() + + '/' + + dt.month.toString() + + '/' + + dt.day.toString()); + if (result != null) { + final String date = DateFormat( + _grid.workbook.cultureInfo.dateTimeFormat.shortDatePattern) + .format(result); + return date; + } + } + + //Below code has been modified to return the General format value when Today formula is interior function. + if (_computedValueLevel > 1) { + return _getSerialDateTimeFromDate(dt).toString(); + } else { + return _getSerialDateFromDate(dt.year, dt.month, dt.day).toString(); + } + } + + /// Returns a single character String. + String _computeConcatenate(String range) { + String text = ''; + final List sb = [_tic]; + + // Below code has been added to calculate the cell ranges(eg:A1:A5B1:B5a) + if (!range.contains(parseArgumentSeparator.toString())) { + range = _adjustRangeArg(range); + } + final List ar = _isSeparatorInTIC(range) ////range.IndexOf(TIC) > 0 + ? _getStringArray(range) + : _splitArgsPreservingQuotedCommas(range); + if (range == null || range.isEmpty) { + return _formulaErrorStrings[_wrongNumberArguments]; + } + for (final String r in ar) { + String toAppend = r; + + final String argumentValue = _getValueFromArg(r); + if (_errorStrings.contains(argumentValue)) { + return argumentValue; + } + if (r.contains(':') && _isCellReference(r)) { + return _errorStrings[1].toString(); + } + if (r == '' || r[0] != _tic[0]) { + toAppend = _getValueFromArg(r); + } + + if (sb.length > 1 && sb[sb.length - 1] == _tic[0]) { + sb.removeAt(sb.length - 1); + } + + if (toAppend.isNotEmpty && toAppend[0] == _tic[0]) { + sb.add(toAppend.substring(1)); + } else { + sb.add(toAppend); + } + } + + if (sb[sb.length - 1] != _tic[0]) { + sb.add(_tic); + } + + text = sb.join(); + if (text.contains('#N/A')) { + text = '#N/A'; + } + + if (excelLikeComputations) { + final String newText = + text.substring(text.indexOf(_tic) + 1, text.lastIndexOf(_tic) - 1); + return newText; + } + return text; + } + + // Accepts a possible parsed formula and returns the calculated value without quotes. + /// + /// + /// This method is useful in custom functions if you want to allow + /// your custom functions to handle parsed formulas as arguments. In + /// this case, calling this method at the beginning of your custom function + /// will allow you custom function to work only with computed values, and not + /// have to handle parsed formulas directly. + /// + String _adjustRangeArg(String range) { + if (range.length > 1 && + range[0] == _bMarker && + range[range.length - 1] == _bMarker && + !range.substring(1, range.length - 2).contains(_bMarker)) { + range = _computedValue(range); + } + + if (range.length > 1 && + range[0] == _tic[0] && + range[range.length - 1] == _tic[0]) { + range = range.substring(1, range.length - 2); + } + return range; + } + + /// Returns True if the ParseArgumentSeparator character is included in a String. + bool _isSeparatorInTIC(String s) { + int i = s.indexOf(_tic) + 1; + bool inTic = true; + while (i > 0 && i < s.length) { + if (s[i] == parseArgumentSeparator && inTic) { + return true; + } + + if (s[i] == _tic[0]) { + inTic = !inTic; + } + + i++; + } + + return false; + } + + /// Returns an array of Strings from an argument list. + List _getStringArray(String s) { + final List argList = []; + + int argStart = 0; + bool inQuote = false; + for (int argEnd = 0; argEnd < s.length; argEnd++) { + final String ch = s[argEnd]; + if (ch == _tic[0]) { + inQuote = !inQuote; + } else if (!inQuote && ch == parseArgumentSeparator) { + argList.add(s.substring(argStart, argEnd - argStart)); + argStart = argEnd + 1; + } + } + + argList.add(s.substring(argStart)); + return argList; + } + + int _getSerialDateFromDate(int y, int m, int d) { + int days = 0; + if (y < 1900) { + y += 1900; + } + + bool isValidMonth = false; + while (!isValidMonth) { + while (m > 12) { + m -= 12; + y++; + } + // to check month as negative or not + while (m < 1 && y > 1900) { + m += 12; + y--; + } + + // to check month and year + if (y < 1900 || (m < 1 && y <= 1900)) { + return -1; + } + + isValidMonth = true; + final date = DateTime(y, m, 1); + int x = DateTime(date.year, date.month + 1, date.day - 1).day; + // to check day with month in the string (for e.g day value as 32303) + while (d > x) { + d -= x; + m++; + if (m > 12) { + m -= 12; + y++; + } + final date = DateTime(y, m, 1); + x = (DateTime(date.year, date.month + 1, date.day - 1)).day; + isValidMonth = false; + } + while (d < 1) { + m--; + final date = DateTime(y, m + 1, 1); + x = (DateTime(date.year, date.month, date.day).add(Duration(hours: -1))) + .day; + d = x + d; + } + } + days = 1 + ((DateTime(y, m, d, 0, 0, 0).difference(_dateTime1900))).inDays; + if (_treat1900AsLeapYear && days > 59) { + days += 1; + } + + return days; + } + + /// Converts text to lowercase. + String _computeLower(String args) { + return _getValueFromArg(args).toLowerCase(); + } + + /// Converts text to uppercase. + String _computeUpper(String args) { + return _getValueFromArg(args).toUpperCase(); + } + + // DateTime _getDateFromSerialDate(int days) { + // days -= 1; + // if (_treat1900AsLeapYear && days > 59) { + // days -= 1; + // } + + // return _dateTime1900.add(Duration(days: days)); + // } } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style.dart index 5cc267831..8596e6b99 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style.dart @@ -21,6 +21,7 @@ class CellStyle implements Style { _builtinId = 0; borders = BordersCollection(_book); isGlobalStyle = false; + locked = true; if (name != null) this.name = name; } @@ -48,7 +49,7 @@ class CellStyle implements Style { @override /// Gets/sets font size. - int fontSize; + double fontSize; @override @@ -108,6 +109,11 @@ class CellStyle implements Style { @override + /// Gets/Sets cell Lock + bool locked; + + @override + /// Sets borders. BordersCollection get borders { _borders ??= BordersCollection(_book); @@ -172,6 +178,7 @@ class CellStyle implements Style { _cellStyle.numberFormat = numberFormat; _cellStyle.numberFormatIndex = numberFormatIndex; _cellStyle.isGlobalStyle = isGlobalStyle; + _cellStyle.locked = locked; _cellStyle.borders = borders._clone(); return _cellStyle; } @@ -196,7 +203,8 @@ class CellStyle implements Style { baseStyle.indent == toCompareStyle.indent && baseStyle.rotation == toCompareStyle.rotation && baseStyle.wrapText == toCompareStyle.wrapText && - baseStyle.borders == toCompareStyle.borders); + baseStyle.borders == toCompareStyle.borders && + baseStyle.locked == toCompareStyle.locked); } @override @@ -219,6 +227,7 @@ class CellStyle implements Style { numberFormat, numberFormatIndex, isGlobalStyle, + locked, borders); /// clear the borders diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_wrapper.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_wrapper.dart index 63e68e94b..0e19f50cc 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_wrapper.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_wrapper.dart @@ -118,8 +118,8 @@ class CellStyleWrapper implements Style { } @override - int get fontSize { - int fontSizeStyle = 11; + double get fontSize { + double fontSizeStyle = 11; bool first = true; final int last = _arrRanges.length; @@ -137,7 +137,7 @@ class CellStyleWrapper implements Style { } @override - set fontSize(int value) { + set fontSize(double value) { final int last = _arrRanges.length; for (int index = 0; index < last; index++) { final Range range = _arrRanges[index]; @@ -509,4 +509,34 @@ class CellStyleWrapper implements Style { (range.cellStyle as CellStyle).isGlobalStyle = value; } } + + @override + + /// Represents the locked. + bool get locked { + bool locked = true; + bool first = true; + + final int last = _arrRanges.length; + for (int index = 0; index < last; index++) { + final Range range = _arrRanges[index]; + + if (first) { + locked = (range.cellStyle as CellStyle).locked; + first = false; + } else if ((range.cellStyle as CellStyle).locked != locked) { + return false; + } + } + return locked; + } + + @override + set locked(bool value) { + final int last = _arrRanges.length; + for (int index = 0; index < last; index++) { + final Range range = _arrRanges[index]; + (range.cellStyle as CellStyle).locked = value; + } + } } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_xfs.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_xfs.dart index 4e60982a0..c68b47160 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_xfs.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_xfs.dart @@ -16,4 +16,7 @@ class CellStyleXfs { /// Represents alignment. Alignment _alignment; + + /// Represent protection. + int _locked; } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/font.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/font.dart index 58f9ee6dd..f221373e9 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/font.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/font.dart @@ -12,6 +12,14 @@ class Font { italic = false; color = 'FF000000'; } + Font._withNameSize(String fontFamilyName, double fontSize) { + name = fontFamilyName; + size = fontSize; + underline = false; + bold = false; + italic = false; + color = 'FF000000'; + } /// Gets/sets font bold. bool bold; @@ -23,7 +31,7 @@ class Font { bool underline; /// Gets/sets font size. - int size; + double size; /// Gets/sets font name. String name; diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/style.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/style.dart index b55885d5d..c8c8fe229 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/style.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/style.dart @@ -18,7 +18,7 @@ class Style { String fontName; /// Gets/sets font size. - int fontSize; + double fontSize; /// Gets/sets font color. String fontColor; @@ -52,4 +52,7 @@ class Style { /// Gets/sets cell numberFormat. String numberFormat; + + /// Gets/Sets cell Lock + bool locked; } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/styles_collection.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/styles_collection.dart index 27d6b8e4f..3073a120a 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/styles_collection.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/styles_collection.dart @@ -406,6 +406,11 @@ class StylesCollection { case 'percent': style.numberFormat = '0%'; break; + + case 'hyperlink': + style.fontColor = '#0000FF'; + style.underline = true; + break; } } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/_constants.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/constants.dart similarity index 100% rename from packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/_constants.dart rename to packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/constants.dart diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/decimal_point_token.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/decimal_point_token.dart index 7f882008b..590c4da0d 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/decimal_point_token.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/decimal_point_token.dart @@ -3,7 +3,7 @@ part of xlsio; ///

/// Class used for describing DecimalSeparatorToken. /// -class _DecimalPointToken extends _SingleCharToken { +class _DecimalPointToken extends _FormatTokenBase { /// /// Format character. /// diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/digit_token.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/digit_token.dart deleted file mode 100644 index 4449fb153..000000000 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/digit_token.dart +++ /dev/null @@ -1,41 +0,0 @@ -part of xlsio; - -/// -/// Class used for describing Digit Tokens. -/// -abstract class _DigitToken extends _SingleCharToken { - /// - /// Applies format to the value. - /// - @override - String _applyFormat(double value, bool bShowHiddenSymbols, - CultureInfo culture, _FormatSection section) { - final int iDigit = 0; - return _getDigitString(value, iDigit, bShowHiddenSymbols); - } - - /// - /// Applies format to the value. - /// - @override - // ignore: unused_element - String _applyFormatString(String value, bool bShowHiddenSymbols) { - return value; - } - - /// - /// Returns string representation according to the current format and digit value. - /// - String _getDigitString(double value, int iDigit, bool bShowHiddenSymbols) { - if (iDigit > 9) { - throw ('iDigit - Value cannot be less than -9 and greater than than 9.'); - } - - return iDigit.toString(); - } - - @override - int _tryParse(String strFormat, int iIndex) { - return iIndex; - } -} diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/_enums.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/enums.dart similarity index 100% rename from packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/_enums.dart rename to packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/enums.dart diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/_format_token_base.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/format_token_base.dart similarity index 100% rename from packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/_format_token_base.dart rename to packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/format_token_base.dart diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/fraction_token.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/fraction_token.dart index 3f7ca8ec9..d83b4976c 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/fraction_token.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/fraction_token.dart @@ -3,20 +3,12 @@ part of xlsio; /// /// Class used for Fraction tokens. /// -class _FractionToken extends _SingleCharToken { +class _FractionToken extends _FormatTokenBase { /// /// Format character. /// static const String _defaultFormatChar = '/'; - /// - /// Initializes a new instance of the FractionToken class. - /// - // ignore: sort_constructors_first - _FractionToken() { - formatChar = _defaultFormatChar; - } - /// /// Applies format to the value. /// @@ -45,11 +37,11 @@ class _FractionToken extends _SingleCharToken { final String chCurrent = strFormat[iIndex]; - if (chCurrent == formatChar) { + if (chCurrent == _defaultFormatChar) { iIndex++; _strFormat = chCurrent.toString(); } else if (strFormat[iIndex] == '\\' && - strFormat[iIndex + 1] == formatChar) { + strFormat[iIndex + 1] == _defaultFormatChar) { _strFormat = strFormat[iIndex + 1].toString(); iIndex = iIndex + 2; } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/milli_second_token.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/milli_second_token.dart index 96cf2caad..27e85767c 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/milli_second_token.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/milli_second_token.dart @@ -39,7 +39,7 @@ class _MilliSecondToken extends _FormatTokenBase { if (iFormatLen < _defaultMaxLen) { final int iPow = _defaultMaxLen - iFormatLen; iMilliSecond = - _FormatSection._round(iMilliSecond / math.pow(10, iPow)).toInt(); + _FormatSection._round(iMilliSecond / pow(10, iPow)).toInt(); strNativeFormat = _strFormat.substring(1, 1 + iFormatLen - 1); } else { strNativeFormat = _defaultFormatLong; diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/significant_digit_token.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/significant_digit_token.dart index 386e3d8fb..86916483b 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/significant_digit_token.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/significant_digit_token.dart @@ -3,12 +3,30 @@ part of xlsio; /// /// Class used for Significant Digit Token. /// -class _SignificantDigitToken extends _DigitToken { +class _SignificantDigitToken extends _FormatTokenBase { /// /// Format character. /// final _defaultFormatChar = '0'; + /// + /// Applies format to the value. + /// + @override + String _applyFormat(double value, bool bShowHiddenSymbols, + CultureInfo culture, _FormatSection section) { + return _strFormat; + } + + /// + /// Applies format to the value. + /// + @override + // ignore: unused_element + String _applyFormatString(String value, bool bShowHiddenSymbols) { + return _strFormat; + } + /// /// Tries to parse format string. /// @@ -30,7 +48,7 @@ class _SignificantDigitToken extends _DigitToken { iIndex++; _strFormat = chCurrent; } else if (strFormat[iIndex] == '\\' && - strFormat[iIndex + 1] == formatChar) { + strFormat[iIndex + 1] == _formatChar) { _strFormat = strFormat[iIndex + 1]; iIndex = iIndex + 2; } @@ -48,7 +66,6 @@ class _SignificantDigitToken extends _DigitToken { /// /// Format character. Read-only. /// - // ignore: unused_element String get _formatChar { if (_strFormat == null) return _defaultFormatChar; return _strFormat; diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/single_char_token.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/single_char_token.dart deleted file mode 100644 index 1dad50efa..000000000 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/formats/format_tokens/single_char_token.dart +++ /dev/null @@ -1,39 +0,0 @@ -part of xlsio; - -/// -/// Class used for character token. -/// -abstract class _SingleCharToken extends _FormatTokenBase { - /// - /// Gets format character. Read-only. - /// - String formatChar = ''; - - /// - /// Tries to parse format string. - /// - @override - int _tryParse(String strFormat, int iIndex) { - if (strFormat == null) throw ('strFormat - string cannot be null'); - - final int iFormatLength = strFormat.length; - - if (iFormatLength == 0) throw ('strFormat - string cannot be empty'); - - if (iIndex < 0 || iIndex > iFormatLength - 1) { - throw ('iIndex - Value cannot be less than 0 and greater than than format length - 1.'); - } - - final String chCurrent = strFormat[iIndex]; - - if (chCurrent == formatChar) { - iIndex++; - _strFormat = chCurrent; - } else if (strFormat[iIndex] == '\\' && - strFormat[iIndex + 1] == formatChar) { - _strFormat = strFormat[iIndex + 1].toString(); - iIndex = iIndex + 2; - } - return iIndex; - } -} diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/autofit_manager.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/autofit_manager.dart new file mode 100644 index 000000000..c413909cc --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/autofit_manager.dart @@ -0,0 +1,698 @@ +part of xlsio; + +/// Represents the class used for autofit columns and rows +class _AutoFitManager { + /// Intializes the AutoFit Manager. + _AutoFitManager( + int row, int column, int lastRow, int lastColumn, Range rangeImpl) { + _row = row; + _column = column; + _lastRow = lastRow; + _lastColumn = lastColumn; + _rangeImpl = rangeImpl; + _worksheet = rangeImpl.worksheet; + _book = rangeImpl.workbook; + } + + /// Intializes the AutoFit Manager. + _AutoFitManager._withSheet(Worksheet worksheet) { + _worksheet = worksheet; + _book = worksheet.workbook; + } + + /// Default size of autofilter font size. + static const double _defaultAutoFilterFontSize = 1.363; + + Range _rangeImpl; + Worksheet _worksheet; + Workbook _book; + int _row; + int _column; + int _lastRow; + int _lastColumn; + + /// Measures the character ranges. + int _measureCharacterRanges( + Style style, String strText, int _num, Rectangle rectF) { + Font font2; + final _FontStyle regular = _FontStyle._regular; + double size = 10; + String familyName = 'Arial'; + Font font; + if (style == null) { + font = _book.fonts[0]; + } else { + font = Font(); + font.name = style.fontName; + font.size = style.fontSize; + } + if (font != null) { + regular._bold = font.bold; + regular._italic = font.italic; + // regular.strikeout = font.strikeout; + regular._underline = font.underline; + familyName = font.name; + size = font.size; + } + + font2 = _createFont(familyName, size, regular); + + if (style.rotation == 90) { + return ((((_getFontHeight(font2) * 1.1) + 0.5)) + 6).toInt(); + } + + final Rectangle bounds = _measureString(strText, font2, rectF, false); + if (style.rotation == 0 || style.rotation == 0xff) { + int num2 = (bounds.width + 0.5).toInt() + _num; + if (num2 > 100) { + num2++; + } + return num2; + } + final int num3 = ((bounds.width + 0.5)).toInt() + _num; + final int num4 = ((_getFontHeight(font2) * 1.1) + 0.5).toInt(); + final double d = (3.1415926535897931 * style.rotation.abs()) / 180.0; + font2 = null; + return (((num3 * cos(d)) + (num4 * sin(d))) + 6.5).toInt(); + } + + /// Measures the character ranges. + int _measureCharacterRangesStyle( + _StyleWithText styleWithText, int paramNum, Rectangle rectF, int column) { + int _num = 0; + final Font font = _createFont( + styleWithText._fontName, styleWithText._size, styleWithText._style); + + int defIndentWidthinPixels = 0; + const int _defaultPixel = 9; + const String _defaultChar = '0'; + final double _indentLevel = _getIndentLevel(column); + for (int i = 0; i < styleWithText._strValues.length; i++) { + String text = styleWithText._strValues[i]; + double indentLevel = _indentLevel; + if (_rangeImpl != null) { + if (indentLevel > 0) { + if (indentLevel < 10) { + defIndentWidthinPixels = (indentLevel * _defaultPixel).toInt(); + text += _defaultChar; + } else { + if (text.length < 255 - text.length) //Max indent level is 255 + { + indentLevel = indentLevel * 2.55; //2.55 is an assumption value + } else if (text.length < 255 - indentLevel) { + indentLevel = + indentLevel * 2.55 + 9; // one indent is equal to 9 pixels. + } else { + indentLevel = ((text.length) * 2 - indentLevel - 9); + } + + for (int idx = 1; idx <= indentLevel; idx++) { + text = ' ' + text; + } + } + } + } + + int num3 = + (_measureString(text, font, rectF, false).width + 0.05).toInt() + + paramNum; + if (defIndentWidthinPixels > 0) { + num3 += defIndentWidthinPixels - paramNum; + } + if (num3 > 100) num3++; + if (_num < num3) _num = num3; + } + + return _num; + } + + Font _createFont(String fontName, double size, _FontStyle fontStyle) { + final Font font = Font(); + font.name = fontName; + font.size = size; + font.bold = fontStyle._bold; + font.italic = fontStyle._italic; + return font; + } + + /// Get Indent level. + double _getIndentLevel(int column) { + if (_rangeImpl.isSingleRange) { + return _rangeImpl.cellStyle.indent.toDouble(); + } else { + double indentLevel = _rangeImpl.cellStyle.indent.toDouble(); + + final int firstRow = _rangeImpl.row; + final int lastRow = _rangeImpl.lastRow; + for (int iRow = firstRow; iRow <= lastRow; iRow++) { + final Range range = _worksheet.getRangeByIndex(iRow, column); + if (indentLevel < range.cellStyle.indent.toDouble()) { + indentLevel = range.cellStyle.indent.toDouble(); + } + } + return indentLevel; + } + } + + double _getFontHeight(Font font) { + return _book._getTextSizeFromFont('a', font)._height; + } + + Rectangle _measureString( + String text, Font font, Rectangle rectF, bool isAutoFitRow) { + return _book._getMeasuredRectangle(text, font, rectF); + } + + /// Sorts the text to fit. + static void _sortTextToFit(List list, Font fontImpl, String strText, + bool autoFilter, HAlignType alignment) { + final _FontStyle regular = _FontStyle._regular; + double size = 10; + String name = 'Arial'; + final Font font = fontImpl; + if (font != null) { + regular._bold = font.bold; + regular._italic = font.italic; + // regular.strikeout = font.strikeout; + regular._underline = font.underline; + name = font.name; + size = font.size; + } + for (int i = 0; i < list.length; i++) { + final _StyleWithText styleWithText = list[i]; + if (((styleWithText._fontName == name) && styleWithText._size == size) && + (styleWithText._style._equals(regular))) { + for (int j = 0; j < styleWithText._strValues.length; j++) { + final String str = styleWithText._strValues[j]; + if (str.length < strText.length) { + styleWithText._strValues.insert(j, strText); + if (styleWithText._strValues.length > 5) { + styleWithText._strValues.removeAt(5); + } + return; + } + } + if (styleWithText._strValues.length < 5) { + styleWithText._strValues.add(strText); + } + return; + } + } + final _StyleWithText swText = _StyleWithText(); + swText._fontName = name; + if (autoFilter && + (alignment != HAlignType.left && alignment != HAlignType.center)) { + swText._size = (size + _defaultAutoFilterFontSize); + // swText._fontSizeIncreased = true; + } else { + swText._size = size; + } + swText._style = regular; + swText._strValues.add(strText); + list.add(swText); + } + + /// Measures to fit column. + void _measureToFitColumn() { + final int _num = 14; + final int firstRow = _row; + final int lastRow = _lastRow; + final int firstColumn = _column; + final int lastColumn = _lastColumn; + final Map> measurable = >{}; + final Map columnsWidth = {}; + final Rectangle ef = Rectangle(0, 0, 1800, 100); + + for (int row = firstRow; row <= lastRow; row++) { + for (int column = firstColumn; column <= lastColumn; column++) { + Range migrantRange = _worksheet.getRangeByIndex(row, column); + final Style style = migrantRange.cellStyle; + Font fontImpl; + if (style == null) { + fontImpl = _book.fonts[0]; + } else { + fontImpl = Font(); + fontImpl.name = style.fontName; + fontImpl.size = style.fontSize; + } + + int num4 = 0; + + final bool autofit = migrantRange._bAutofitText; + final List result = Range._isMergedCell(migrantRange, false, num4); + num4 = result[0]; + final bool isMerged = result[1]; + + if (!isMerged || num4 != 0) { + if (!autofit) { + migrantRange._bAutofitText = true; + } + + if (!columnsWidth.containsKey(column)) { + columnsWidth[column] = 0; + } + String text = migrantRange.displayText; + if (text == null || text == '') { + continue; + } + + migrantRange = _worksheet.getRangeByIndex(row, column); + final bool hasWrapText = (style.wrapText); + if (!hasWrapText) { + text = text.replaceAll('\n', ''); + } + if ((style.rotation == 0 || style.rotation == 0xff) && !hasWrapText) { + List arrList = + (measurable.containsKey(column)) ? measurable[column] : null; + if (arrList == null) { + arrList = []; + measurable[column] = arrList; + } + final HAlignType horizontalAlignment = style.hAlign; + if (horizontalAlignment == HAlignType.center) { + final Range cellRange = + _rangeImpl.worksheet.getRangeByIndex(row, column++); + if (column != cellRange.column + 1) { + continue; + } + } + _sortTextToFit(arrList, fontImpl, text, false, horizontalAlignment); + } else if (hasWrapText) { + final int columnWidth = _worksheet._getColumnWidthInPixels(column); + final double textHeight = + _calculateWrappedCell(style, text, columnWidth); + final double fitRowHeight = _book._convertFromPixel(textHeight, 6); + + final double rowHeight = migrantRange.rowHeight; + final bool isCustomHeight = + false; //rowStorage != null ? rowStorage.IsBadFontHeight : false; + List words; + final List wordsN = text.split('\n'); + final List wordNSplit = + wordsN[wordsN.length - 1].split(' '); + words = List(wordsN.length - 1 + wordNSplit.length); + for (int i = 0; i < wordsN.length - 1; i++) { + words[i] = wordsN[i] + '\n'; + } + int j = wordsN.length - 1; + for (int i = 0; i < wordNSplit.length; i++) { + words[j] = wordNSplit[i]; + j++; + } + String autoFitText; + int biggestLength = 0; + for (int index = 0; index < words.length; index++) { + autoFitText = words[index].toString(); + if (autoFitText.isNotEmpty) { + final int length = + _measureCharacterRanges(style, autoFitText, _num, ef); + final CellType cellType = migrantRange.type; + final bool isNumberCellType = cellType == CellType.number; + if (length < columnWidth || isNumberCellType) { + for (int temp = index + 1; + temp < words.length || temp == 1; + temp++) { + index = temp; + if (words.length != 1) { + if (!autoFitText.endsWith('\n')) { + autoFitText = autoFitText + ' ' + words[temp]; + } else { + index--; + } + } + final int currentLength = + _measureCharacterRanges(style, autoFitText, _num, ef); + if (wordsN.length == 1 && + (currentLength > biggestLength) && + isCustomHeight && + rowHeight >= _worksheet._standardHeight && + rowHeight <= fitRowHeight) { + biggestLength = currentLength; + } else if (currentLength < columnWidth || + isNumberCellType) { + if (currentLength > biggestLength) { + biggestLength = currentLength; + temp = words.length; + } + } else { + index = temp - 1; + biggestLength = columnWidth; + temp = words.length; + } + } + } else if (style.rotation == 0 || style.rotation == 0xff) { + if (wordsN.length == 1 && + length > biggestLength && + isCustomHeight && + rowHeight >= _worksheet._standardHeight && + rowHeight <= fitRowHeight) { + biggestLength = length; + } else { + biggestLength = columnWidth; + } + index = words.length; + } else if (length > biggestLength) { + biggestLength = length; + } + } + if (biggestLength > columnsWidth[column]) { + columnsWidth[column] = biggestLength; + } + } + } else { + final int num5 = _measureCharacterRanges(style, text, _num, ef); + final int num6 = + (columnsWidth.containsKey(column)) ? columnsWidth[column] : 0; + if (num6 < num5) { + columnsWidth[column] = num5; + } + } + } + } + } + for (final int key in measurable.keys) { + final List list3 = measurable[key]; + int num8 = 0; + for (int k = 0; k < list3.length; k++) { + final _StyleWithText styleWithText = list3[k] as _StyleWithText; + final int num10 = + _measureCharacterRangesStyle(styleWithText, _num, ef, key); + if (num8 < num10) num8 = num10; + } + final int num11 = (columnsWidth.containsKey(key)) ? columnsWidth[key] : 0; + if (num8 > num11) columnsWidth[key] = num8; + } + + for (final int key in columnsWidth.keys) { + final int num12 = columnsWidth[key]; + if (num12 != 0) { + _worksheet._setColumnWidthInPixels(key, num12, true); + } + } + } + + double _calculateWrappedCell( + Style format, String stringValue, int columnWidth) { + final Font font = Font(); + font.name = format.fontName; + font.size = format.fontSize; + double num9 = 0; + double num6 = 0; + final int number = 19; + + if (stringValue == null || stringValue.isEmpty) { + return 0; + } else { + final double calculatedValue = ((stringValue.length / 406) * (font.size) + + (2 * ((font.bold || font.italic) ? 1 : 0))); + num9 = (calculatedValue < columnWidth) + ? columnWidth.toDouble() + : calculatedValue; + num6 = _measureCell(format, stringValue, num9, number, true); + return num6; + } + } + + double _measureCell(Style format, String stringValue, double columnWidth, + int number, bool isString) { + final Font font = Font(); + font.name = format.fontName; + font.size = format.fontSize; + double size = font.size; + final _FontStyle regular = _FontStyle._regular; + + if (stringValue[stringValue.length - 1] == '\n') { + stringValue = stringValue + 'a'; + } + + regular._bold = font.bold; + regular._italic = font.italic; + // regular.strikeout = font.strikeout; + regular._underline = font.underline; + size = font.size; + + if (isString && (font.name == 'Times New Roman')) { + stringValue = _modifySepicalChar(stringValue); + } + + final Font font2 = _createFont(font.name, size, regular); + final double num2 = 0; + final double num3 = 0; + final double num4 = 600; + if (!format.wrapText) { + columnWidth = 600; + } else if (columnWidth < 100) { + if (format.hAlign == HAlignType.left || + format.hAlign == HAlignType.right) { + columnWidth = (columnWidth - 1); + } + } else { + columnWidth = (columnWidth - 2); + } + + final Rectangle ef = Rectangle(num2, num3, columnWidth, num4); + final Rectangle bounds = _measureString(stringValue, font2, ef, true); + double num5; + + num5 = (bounds.height).ceilToDouble(); + + if ((font.size >= 20) || (num5 > 100)) { + num5 = num5 + 1; + } + if (format.wrapText) { + if (size >= 10) { + return num5; + } + final int num6 = _calculateFontHeight(font); + double num7 = bounds.height; + if (num7 > 100) { + num7 = (num7 + 1); + } + int num8 = ((num7 * 1.0) / (num6)).ceil(); + if (num7 > 100) { + num8 = ((((num7 * 1.0) / (num6))) + 1).toInt(); + } + if (num8 == 1) { + return _calculateFontHeightFromGraphics(font); + } + final StringBuffer buffer = StringBuffer(); + for (int i = 0; i < num8; i = (i + 1)) { + buffer.write('0'); + if (i + 1 < num8) { + buffer.write('\n'); + } + } + return _measureFontSize(format, buffer.toString(), columnWidth); + } + final int num10 = format.rotation.abs(); + if (num10 == 90) { + return (((bounds.width + 0.5)) + number); + } + final int num11 = (((bounds.width + 0.5)) + number); + final int num12 = ((_getFontHeight(font2) * 1.1) + 0.5).toInt(); + return ((((num11 * sin(((3.1415926535897931 * num10) / 180.0))) + + (num12 * cos(((3.1415926535897931 * num10) / 180.0)))) + + 6.5)); + } + + double _calculateFontHeightFromGraphics(Font font) { + final double size = font.size; + final _FontStyle regular = _FontStyle._regular; + regular._bold = font.bold; + regular._italic = font.italic; + // regular.strikeout = font.strikeout; + regular._underline = font.underline; + final Font font2 = _createFont(font.name, size, regular); + + double num3 = _getFontHeight(font2).ceilToDouble(); + if (((font.size >= 20) || (num3 > 100)) || + ((font.size == 12) && font.bold)) { + num3 = (num3 + 1); + } + if (font.size == 8) { + num3 = (num3 + 2); + } else if (font.size < 10) { + num3 = (num3 + 1); + } + return num3; + } + + int _calculateFontHeight(Font font) { + final double size = font.size; + final _FontStyle regular = _FontStyle._regular; + regular._bold = font.bold; + regular._italic = font.italic; + // regular.strikeout = font.strikeout; + regular._underline = font.underline; + + final Font font2 = _createFont(font.name, size, regular); + return (_getFontHeight(font2)).ceil(); + } + + double _measureFontSize( + Style extendedFromat, String stringValue, double columnWidth) { + if (stringValue.isEmpty) { + return 0; + } + final double size = extendedFromat.fontSize; + final Font font = + _createFont(extendedFromat.fontName, size, _FontStyle._regular); + + final double num2 = 0; + final double num3 = 0; + final double num4 = 600; + if (!extendedFromat.wrapText) { + columnWidth = 600; + } + final Rectangle ef = Rectangle(num2, num3, columnWidth, num4); + return ((_measureString(stringValue, font, ef, true).height * 1.1 + 0.5)); + } + + String _modifySepicalChar(String stringValue) { + final StringBuffer buffer = StringBuffer(); + for (int i = 0; i < stringValue.length; i++) { + switch (stringValue[i]) { + case ' ': + if (i != 0) { + switch (stringValue[i - 1]) { + case '%': + case '&': + buffer.write(stringValue[i]); + break; + } + } + buffer.write(stringValue[i]); + continue; + + case '/': + { + buffer.write('W'); + continue; + } + default: + { + buffer.write(stringValue[i]); + continue; + } + } + buffer.write(stringValue[i - 1]); + buffer.write(stringValue[i]); + } + return buffer.toString(); + } +} + +/// Collection of DisplayText with matching fonts. This class used to improve the AutoFitToColumn method Performance. +class _StyleWithText { + /// Creates an new instances of the Workbook. + _StyleWithText() { + _strValues = []; + } + String _fontName; + double _size; + _FontStyle _style; + List _strValues; + // bool _fontSizeIncreased; +} + +/// Represents Font style class. +class _FontStyle { + // ignore: prefer_final_fields + static _FontStyle _regular = _FontStyle(); + bool _bold; + bool _italic; + // bool _strikeout; + bool _underline; + + bool _equals(_FontStyle style) { + return _bold == style._bold && + _italic == style._italic && + _underline == style._underline; + } +} + +/// Metrics of the font. +class _FontMetrics { + /// Initialize the font Metrics class with the parameters. + _FontMetrics(double ascent, double descent, int linegap, double height, + double superscriptfactor, double subscriptfactor) { + _ascent = ascent; + _descent = descent; + _lineGap = linegap; + _height = height; + _superscriptSizeFactor = superscriptfactor; + _subScriptSizeFactor = subscriptfactor; + } + + /// Gets ascent of the font. + double _ascent; + + /// Gets descent of the font. + double _descent; + + /// Line gap. + int _lineGap; + + /// Gets height of the font. + // ignore: unused_field + double _height; + + /// Subscript size factor. + // ignore: unused_field + double _subScriptSizeFactor; + + /// Superscript size factor. + // ignore: unused_field + double _superscriptSizeFactor; + + /// Multiplier of the symbol width. + static const double _chartSizeMultiplier = 0.001; + + /// Returns ascent taking into consideration font's size. + double _getAscent(Font format) { + return (_ascent * _chartSizeMultiplier * _getSize(format)); + } + + /// Returns descent taking into consideration font's size. + double _getDescent(Font format) { + return (_descent * _chartSizeMultiplier * _getSize(format)); + } + + /// Returns Line gap taking into consideration font's size. + double _getLineGap(Font format) { + return (_lineGap * _chartSizeMultiplier * _getSize(format)); + } + + /// Returns height taking into consideration font's size. + double _getHeight(Font format) { + double height; + if (_getDescent(format) < 0) { + height = (_getAscent(format) - _getDescent(format) + _getLineGap(format)); + } else { + height = (_getAscent(format) + _getDescent(format) + _getLineGap(format)); + } + + return height; + } + + /// Calculates size of the font depending on the subscript/superscript value. + double _getSize(Font format) { + final double size = format.size; + return size; + } +} + +class _SizeF { + _SizeF(double width, double height) { + _width = width; + _height = height; + } + static _SizeF _empty; + double _width; + double _height; + + // ignore: unused_element + bool get _isEmpty { + return ((_width == 0) && (_height == 0)); + } +} diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/enums.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/enums.dart index 463c40876..b345f2f30 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/enums.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/enums.dart @@ -69,49 +69,33 @@ enum LineStyle { /// Possible format types. enum ExcelFormatType { - /// /// Represents unknown format type. - /// unknown, - /// /// Represents general number format. - /// general, - /// /// Represents text number format. - /// text, - /// /// Represents number number format. - /// number, - /// /// Represents datetime number format. - /// dateTime, /// /// Represents percentage number format. - /// + percentage, - /// /// Represents currency number format. - /// currency, - /// /// Represents decimal percentage number format. - /// decimalPercentage, - /// /// Represents Exponential number format. - /// exponential, } @@ -257,4 +241,111 @@ enum BuiltInStyles { /// Indicates Explanatory Text style. explanatoryText, + + /// Indicates Hyperlink style. + hyperlink, +} + +/// Possible types of hyperlinks. + +enum HyperlinkType { + /// No hyperlink. + none, + + /// Represents the Url hyperlink type. + url, + + /// Represents the File hyperlink type. + + file, + + /// Represents the Unc hyperlink type. + unc, + + /// Represents the Workbook hyperlink type. + workbook +} + +/// Represents a possible insert options in Excel. +enum ExcelInsertOptions { + /// Indicates that after insert operation inserted rows/columns + /// must be formatted as row above or column left. + formatAsBefore, + + /// Indicates that after insert operation inserted rows/columns + /// must be formatted as row below or column right. + formatAsAfter, + + /// Indicates that after insert operation inserted rows/columns + /// must have default format. + formatDefault, +} + +/// Represents sheet protection flags enums. +enum ExcelSheetProtection { + /// Represents none flags. + none, + + /// True to protect shapes. + objects, + + /// True to protect scenarios. + scenarios, + + /// True allows the user to format any cell on a protected worksheet. + formattingCells, + + /// True allows the user to format any column on a protected worksheet. + formattingColumns, + + /// True allows the user to format any row on a protected. + formattingRows, + + /// True allows the user to insert columns on the protected worksheet. + insertingColumns, + + /// True allows the user to insert rows on the protected worksheet. + insertingRows, + + /// True allows the user to insert hyperlinks on the worksheet. + insertingHyperlinks, + + /// True allows the user to delete columns on the protected worksheet, + /// where every cell in the column to be deleted is unlocked. + deletingColumns, + + /// True allows the user to delete rows on the protected worksheet, + /// where every cell in the row to be deleted is unlocked. + deletingRows, + + /// True to protect locked cells. + lockedCells, + + /// True allows the user to sort on the protected worksheet. + sorting, + + /// True allows the user to set filters on the protected worksheet. + /// Users can change filter criteria but can not enable or disable an auto filter. + filtering, + + /// True allows the user to use pivot table reports on the protected worksheet. + usingPivotTables, + + /// True to protect the user interface, but not macros. + unLockedCells, + + /// True to protect content. + content, + + /// Represents all flags + all, +} + +/// Specify the hyperlink attached object name. +enum ExcelHyperlinkAttachedType { + /// Represent IRange object. + range, + + /// Represent IShape object. + shape, } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/serialize_workbook.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/serialize_workbook.dart index d51eea6ef..bf9e6b43f 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/serialize_workbook.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/serialize_workbook.dart @@ -10,6 +10,9 @@ class SerializeWorkbook { /// Workbook to serialize. Workbook _workbook; + /// Relation id + final List _relationId = []; + /// Serialize workbook void _saveInternal() { _updateGlobalStyles(); @@ -39,13 +42,12 @@ class SerializeWorkbook { builder.attribute('codeName', 'ThisWorkbook'); builder.attribute('defaultThemeVersion', '153222'); }); - + _serializeWorkbookProtection(builder); builder.element('bookViews', nest: () { builder.element('workbookView', nest: () { builder.attribute('activeTab', '0'); }); }); - builder.element('sheets', nest: () { for (int i = 0; i < _workbook.worksheets.count; i++) { builder.element('sheet', nest: () { @@ -61,15 +63,67 @@ class SerializeWorkbook { _addToArchive(bytes, 'xl/workbook.xml'); } + /// Serializes workbook protection options. + void _serializeWorkbookProtection(final builder) { + if (_workbook._bWindowProtect || _workbook._bCellProtect) { + builder.element('workbookProtection', nest: () { + if (_workbook._password != null) { + final int usPassword = _workbook._isPassword; + if (usPassword != 0) { + builder.attribute('workbookPassword', usPassword.toRadixString(16)); + } + } + _serializeAttributes( + builder, 'lockStructure', _workbook._bCellProtect, false); + _serializeAttributes( + builder, 'lockWindows', _workbook._bWindowProtect, false); + }); + } + } + + /// serialize Attributes + void _serializeAttributes( + final builder, String attributeName, bool value, bool defaultValue) { + String strValue; + if (value != defaultValue) { + strValue = value ? '1' : '0'; + } + if (strValue != null) builder.attribute(attributeName, strValue); + } + /// Serialize worksheets. void _saveWorksheets() { final length = _workbook.worksheets.count; for (int i = 0; i < length; i++) { final worksheet = _workbook.worksheets[i]; + _updateHyperlinkCells(worksheet); _saveWorksheet(worksheet, i); } } + /// Update the Hyperlink cells + void _updateHyperlinkCells(Worksheet worksheet) { + for (final Hyperlink hyperlink in worksheet.hyperlinks.innerList) { + final Row row = worksheet.rows._getRow(hyperlink._row); + if (row != null) { + final Range cell = row.ranges._getCell(hyperlink._column); + if (cell != null) { + if (hyperlink.textToDisplay != null && + cell.number == null && + cell.text == null) { + cell.value = hyperlink.textToDisplay; + } else if (cell.text != null) { + cell.value = cell.text; + } else if (cell.number != null) { + cell.value = cell.number; + } else { + cell.value = hyperlink.address; + } + } + } + } + } + /// Serialize worksheet. void _saveWorksheet(Worksheet sheet, int index) { final builder = XmlBuilder(); @@ -135,47 +189,48 @@ class SerializeWorkbook { cell._styleIndex = -1; } builder.element('c', nest: () { + String strFormula = cell.formula; builder.attribute('r', cell.addressLocal); - if (cell._saveType != null) { + if (cell._saveType != null && + (strFormula == null || + strFormula == '' || + strFormula[0] != '=' || + cell._saveType == 's')) { builder.attribute('t', cell._saveType); } if (cell._styleIndex != -1) { builder.attribute('s', cell._styleIndex.toString()); } - String strFormula = cell.formula; - if (strFormula != null && strFormula != '') { - if (strFormula[0] == '=' && cell._saveType != 's') { - strFormula = - strFormula.substring(1).replaceAll('\'', '\"'); - builder.element('f', nest: strFormula); - } - if (sheet.calcEngine != null && - cell.number == null && - cell.text == null && - cell._boolean == null && - cell._errorValue == null) { - builder.element('v', - nest: cell.calculatedValue.toString()); - } else if (cell._errorValue != null) { - builder.element('v', nest: cell._errorValue); - } else if (cell._boolean != null) { - builder.element('v', nest: cell._boolean); - } else if (cell.number != null) { - builder.element('v', nest: cell.number.toString()); - } else if (cell.text != null) { - if (cell._saveType == 's') { - builder.element('v', - nest: cell._textIndex.toString()); - } else { - builder.element('v', nest: cell.text); - } - } + String cellValue; + if (sheet.calcEngine != null && + cell.number == null && + cell.text == null && + cell._boolean == null && + cell._errorValue == null) { + cellValue = cell.calculatedValue; + } else if (cell._errorValue != null) { + cellValue = cell._errorValue; + } else if (cell._boolean != null) { + cellValue = cell._boolean; } else if (cell.number != null) { - builder.element('v', nest: cell.number.toString()); + cellValue = cell.number.toString(); } else if (cell.text != null) { - builder.element('v', - nest: cell._textIndex.toString()); + if (cell._saveType == 's') { + cellValue = cell._textIndex.toString(); + } else { + cellValue = cell.text; + } + } + if (strFormula != null && + strFormula != '' && + strFormula[0] == '=' && + cell._saveType != 's') { + builder.attribute('t', cell._saveType); + strFormula = + strFormula.substring(1).replaceAll('\'', '\"'); + builder.element('f', nest: strFormula); } + builder.element('v', nest: cellValue); }); } } @@ -185,6 +240,31 @@ class SerializeWorkbook { } } }); + if (sheet._isPasswordProtected) { + final ExcelSheetProtectionOption protection = sheet._innerProtection; + builder.element('sheetProtection', nest: () { + if (sheet._algorithmName != null) { + builder.attribute('algorithmName', sheet._algorithmName); + builder.attribute('hashValue', base64.encode(sheet._hashValue)); + builder.attribute('saltValue', base64.encode(sheet._saltValue)); + builder.attribute('spinCount', sheet._spinCount); + } else if (sheet._isPassword != 1) { + final String password = sheet._isPassword.toRadixString(16); + builder.attribute('password', password); + } + List attributes; + List flags; + List defaultValues; + attributes = sheet._protectionAttributes; + flags = sheet._flag; + defaultValues = sheet._defaultValues; + // ignore: prefer_final_locals + for (int i = 0, iCount = attributes.length; i < iCount; i++) { + _serializeProtectionAttribute( + builder, attributes[i], flags[i], defaultValues[i], protection); + } + }); + } if (sheet.mergeCells != null && sheet.mergeCells.innerList.isNotEmpty) { builder.element('mergeCells', nest: () { builder.attribute( @@ -196,7 +276,7 @@ class SerializeWorkbook { } }); } - + _serializeHyperlinks(builder, sheet); builder.element('pageMargins', nest: () { builder.attribute('left', '0.75'); builder.attribute('right', '0.75'); @@ -219,7 +299,22 @@ class SerializeWorkbook { sheet.charts.serializeCharts(sheet); } builder.element('drawing', nest: () { - builder.attribute('r:id', 'rId1'); + int id = 1; + final String rId = 'rId1'; + if (_relationId != null) { + if (sheet.hyperlinks.count > 0) { + for (int i = 0; i < sheet.hyperlinks.count; i++) { + if (sheet.hyperlinks[i]._attachedType == + ExcelHyperlinkAttachedType.range && + sheet.hyperlinks[i].type != HyperlinkType.workbook) { + id++; + } + } + } + builder.attribute('r:id', 'rId' + id.toString()); + } else { + builder.attribute('r:id', rId); + } }); } final rel = _saveSheetRelations(sheet); @@ -232,6 +327,59 @@ class SerializeWorkbook { bytes, 'xl/worksheets' '/sheet' + (index + 1).toString() + '.xml'); } + /// Serializes single protection option. + void _serializeProtectionAttribute(final builder, String attributeName, + bool flag, bool defaultValue, ExcelSheetProtectionOption protection) { + final bool value = flag; + _serializeAttributes(builder, attributeName, value, defaultValue); + } + + /// Serialize hyperlinks + void _serializeHyperlinks(final builder, Worksheet sheet) { + if (sheet.hyperlinks != null && sheet.hyperlinks.count > 0) { + final int iCount = sheet.hyperlinks.count; + final List hyperLinkType = List(iCount); + for (int i = 0; i < sheet.hyperlinks.count; i++) { + final Hyperlink hyperLink = sheet.hyperlinks[i]; + hyperLinkType[i] = hyperLink._attachedType + .toString() + .split('.') + .toList() + .removeAt(1) + .toString(); + } + if (iCount == 0 || !hyperLinkType.contains('range')) { + return; + } + builder.element('hyperlinks', nest: () { + for (final Hyperlink link in sheet.hyperlinks.innerList) { + if (link._attachedType == ExcelHyperlinkAttachedType.range) { + builder.element('hyperlink', nest: () { + if (link.type == HyperlinkType.workbook) { + builder.attribute('ref', link.reference); + builder.attribute('location', link.address); + } else { + builder.attribute('ref', link.reference); + final String id = link._rId.toString(); + final String rId = 'rId' + id.toString(); + builder.attribute('r:id', rId); + _relationId.add(rId); + } + if (link.screenTip != null) { + builder.attribute('tooltip', link.screenTip); + } + if (link.textToDisplay != null) { + builder.attribute('display', link.textToDisplay); + } else { + builder.attribute('display', link.address); + } + }); + } + } + }); + } + } + /// Serialize drawings void _saveDrawings(Worksheet sheet) { final builder = XmlBuilder(); @@ -246,8 +394,11 @@ class SerializeWorkbook { sheet.charts.serializeChartDrawing(builder, sheet); } final idIndex = 0 + chartCount; + final List idRelation = []; if (sheet.pictures.count != 0) { int imgId = 0; + int idHyperlink = 1; + int hyperlinkCount = 0; for (final Picture picture in sheet.pictures.innerList) { if (picture.height != 0 && picture.width != 0) { if (picture.lastRow == null || @@ -285,12 +436,31 @@ class SerializeWorkbook { }); builder.element('xdr:pic', nest: () { + builder.attribute('macro', ''); builder.element('xdr:nvPicPr', nest: () { builder.element('xdr:cNvPr', nest: () { builder.attribute('id', imgId); builder.attribute('name', 'Picture' + imgId.toString()); + if (picture._isHyperlink) { + builder.element('a:hlinkClick', nest: () { + builder.attribute('xmlns:r', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + int id = idIndex + imgId + idHyperlink; + String rId = 'rId' + id.toString(); + if (idRelation.contains(rId)) { + id = id + 1; + rId = 'rId' + id.toString(); + } + builder.attribute('r:id', rId); + idRelation.add(rId); + sheet._hyperlinkRelationId.add(rId); + idHyperlink++; + if (picture._link.screenTip != null) { + builder.attribute('tooltip', picture._link.screenTip); + } + }); + } }); - builder.element('xdr:cNvPicPr', nest: () { builder.element('a:picLocks', nest: () { builder.attribute('noChangeAspect', 1); @@ -302,8 +472,30 @@ class SerializeWorkbook { builder.element('a:blip', nest: () { builder.attribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); - builder.attribute( - 'r:embed', 'rId' + (idIndex + imgId).toString()); + int id; + String rId; + if (idRelation == null) { + id = idIndex + imgId + hyperlinkCount; + rId = 'rId' + id.toString(); + builder.attribute('r:embed', rId); + idRelation.add(rId); + } else { + id = idIndex + imgId + hyperlinkCount; + rId = 'rId' + id.toString(); + if (idRelation.contains(rId)) { + id = id + 1; + rId = 'rId' + id.toString(); + if (idRelation.contains(rId)) { + id = id + 1; + rId = 'rId' + id.toString(); + } + } + builder.attribute('r:embed', rId); + idRelation.add(rId); + if (picture._isHyperlink) { + hyperlinkCount++; + } + } builder.attribute('cstate', 'print'); }); @@ -386,9 +578,35 @@ class SerializeWorkbook { } idIndex = length; } + if (sheet.hyperlinks != null && sheet.hyperlinks.count > 0) { + final length = sheet.hyperlinks.count; + int j = 0; + for (int i = 0; i < length; i++) { + if (sheet.hyperlinks[i]._attachedType == + ExcelHyperlinkAttachedType.shape) { + builder.element('Relationship', nest: () { + builder.attribute('Id', sheet._hyperlinkRelationId[j]); + builder.attribute('Type', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink'); + if (sheet.hyperlinks[i].type == HyperlinkType.workbook) { + String address = sheet.hyperlinks[i].address; + address = address.startsWith('#') ? address : '#' + address; + builder.attribute('Target', address); + } else { + builder.attribute('Target', sheet.hyperlinks[i].address); + } + if (sheet.hyperlinks[i].type != HyperlinkType.workbook) { + builder.attribute('TargetMode', 'External'); + } + }); + j++; + } + } + } if (sheet.pictures.count != 0) { final length = sheet.pictures.count; int id = _workbook._imageCount - sheet.pictures.count; + int idHyperlink = 0; for (int i = 1; i <= length; i++) { id++; builder.element('Relationship', nest: () { @@ -398,10 +616,20 @@ class SerializeWorkbook { } else { imgPath = '/xl/media/image' + id.toString() + '.jpeg'; } - builder.attribute('Id', 'rId' + (idIndex + i).toString()); - builder.attribute('Type', - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image'); - builder.attribute('Target', imgPath); + if (sheet.pictures[i - 1]._isHyperlink) { + builder.attribute( + 'Id', 'rId' + (idIndex + i + idHyperlink).toString()); + builder.attribute('Type', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image'); + builder.attribute('Target', imgPath); + idHyperlink++; + } else { + builder.attribute( + 'Id', 'rId' + (idIndex + i + idHyperlink).toString()); + builder.attribute('Type', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image'); + builder.attribute('Target', imgPath); + } }); } } @@ -426,11 +654,11 @@ class SerializeWorkbook { double iRowHeight; if (sheet.rows.count != 0 && iCurRow - 1 < sheet.rows.count && - sheet.rows[iCurRow - 1] != null) { - iRowHeight = _convertToPixels((sheet.rows[iCurRow - 1].height == null || - sheet.rows[iCurRow - 1].height == 0) + sheet.rows[iCurRow] != null) { + iRowHeight = _convertToPixels((sheet.rows[iCurRow].height == null || + sheet.rows[iCurRow].height == 0) ? 15 - : sheet.rows[iCurRow - 1].height); + : sheet.rows[iCurRow].height); } else { iRowHeight = _convertToPixels(15); } @@ -441,13 +669,13 @@ class SerializeWorkbook { picture.lastRowOffset = (iCurOffset + (iCurHeight * 256 / iRowHeight)); double rowHiddenHeight; if (sheet.rows.count != 0 && - iCurRow - 1 < sheet.rows.count && - sheet.rows[iCurRow - 1] != null) { + iCurRow < sheet.rows.count && + sheet.rows[iCurRow] != null) { rowHiddenHeight = _convertToPixels( - (sheet.rows[iCurRow - 1].height == null || - sheet.rows[iCurRow - 1].height == 0) + (sheet.rows[iCurRow].height == null || + sheet.rows[iCurRow].height == 0) ? 15 - : sheet.rows[iCurRow - 1].height); + : sheet.rows[iCurRow].height); } else { rowHiddenHeight = _convertToPixels(15); } @@ -497,7 +725,7 @@ class SerializeWorkbook { colHiddenWidth = _columnWidthToPixels( sheet.columns[iCurCol - 1].width == 0 ? 8.43 - : sheet.columns[iCurCol].width); + : sheet.columns[iCurCol - 1].width); } else { colHiddenWidth = _columnWidthToPixels(8.43); } @@ -541,14 +769,43 @@ class SerializeWorkbook { /// Serialize sheet relations. List _saveSheetRelations(Worksheet sheet) { final builder = XmlBuilder(); - builder.processing('xml', 'version="1.0"'); builder.element('Relationships', nest: () { builder.namespace( 'http://schemas.openxmlformats.org/package/2006/relationships'); + + if (sheet.hyperlinks != null && sheet.hyperlinks.count > 0) { + for (final Hyperlink link in sheet.hyperlinks.innerList) { + if (link._attachedType == ExcelHyperlinkAttachedType.range && + link.type != HyperlinkType.workbook) { + builder.element('Relationship', nest: () { + builder.attribute('Id', 'rId' + link._rId.toString()); + builder.attribute('Type', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink'); + builder.attribute('Target', link.address); + builder.attribute('TargetMode', 'External'); + }); + } + } + } if (sheet.pictures != null && sheet.pictures.count > 0) { builder.element('Relationship', nest: () { - builder.attribute('Id', 'rId1'); + int id = 1; + final String rId = 'rId1'; + if (_relationId != null) { + if (sheet.hyperlinks.count > 0) { + for (int i = 0; i < sheet.hyperlinks.count; i++) { + if (sheet.hyperlinks[i]._attachedType == + ExcelHyperlinkAttachedType.range && + sheet.hyperlinks[i].type != HyperlinkType.workbook) { + id++; + } + } + } + builder.attribute('Id', 'rId' + id.toString()); + } else { + builder.attribute('Id', rId); + } builder.attribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing'); builder.attribute( @@ -559,7 +816,22 @@ class SerializeWorkbook { }); } else if (sheet.charts != null && sheet.chartCount > 0) { builder.element('Relationship', nest: () { - builder.attribute('Id', 'rId1'); + int id = 1; + final String rId = 'rId1'; + if (_relationId != null) { + if (sheet.hyperlinks.count > 0) { + for (int i = 0; i < sheet.hyperlinks.count; i++) { + if (sheet.hyperlinks[i]._attachedType == + ExcelHyperlinkAttachedType.range && + sheet.hyperlinks[i].type != HyperlinkType.workbook) { + id++; + } + } + } + builder.attribute('Id', 'rId' + id.toString()); + } else { + builder.attribute('Id', rId); + } builder.attribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing'); builder.attribute( @@ -579,7 +851,7 @@ class SerializeWorkbook { builder.element('sheetViews', nest: () { builder.element('sheetView', nest: () { builder.attribute('workbookViewId', '0'); - if (!sheet.showGridLines) { + if (!sheet.showGridlines) { builder.attribute('showGridLines', '0'); } }); @@ -942,7 +1214,9 @@ class SerializeWorkbook { /// Process the cell style. int _processCellStyle(CellStyle style, Workbook workbook) { final int index = workbook.styles.innerList.indexOf(style); - if (style.isGlobalStyle && index >= 0) { + if (style.isGlobalStyle && + index >= 0 && + index > workbook.styles.innerList.length - 1) { _processNumFormatId(style, workbook); if (style.name == null) workbook.styles.addStyle(style); return style.index; @@ -1039,6 +1313,10 @@ class SerializeWorkbook { cellXfs._alignment.wrapText = style.wrapText ? 1 : 0; cellXfs._alignment.rotation = style.rotation; + // Add protection + if (!style.locked) { + cellXfs._locked = style.locked ? 1 : 0; + } if (style.isGlobalStyle) { _workbook._cellStyleXfs.add(cellXfs); _workbook._cellXfs.add(cellXfs as CellXfs); @@ -1226,12 +1504,20 @@ class SerializeWorkbook { builder.attribute('borderId', cellXf._borderId.toString()); builder.attribute('xfId', cellXf._xfId.toString()); _saveAlignment(cellXf, builder); + _saveProtection(cellXf, builder); }); } } }); } + ///Serialize Protection. + void _saveProtection(CellStyleXfs cellXf, final builder) { + builder.element('protection', nest: () { + builder.attribute('locked', cellXf._locked.toString()); + }); + } + /// Serialize alignment. void _saveAlignment(CellStyleXfs cellXf, final builder) { builder.element('alignment', nest: () { diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/workbook.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/workbook.dart index 74ae4ca53..7fff754ba 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/workbook.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/workbook.dart @@ -31,6 +31,9 @@ class Workbook { /// Represents the maximum column count in the workbook. final int _maxColumnCount = 16384; + /// Maximum digit width (used to evaluate different column width). + final double _dMaxDigitWidth = 7.0; + /// Represents the cell style collection in the workbok. Map _cellStyles; @@ -98,6 +101,9 @@ class Workbook { final Map _defaultContentTypes = {}; + /// Represents the Hyperlink collection. + HyperlinkCollection _hyperlink; + /// Represents the unit conversion list. List _unitsProportions = [ 96 / 75.0, // Display @@ -110,6 +116,12 @@ class Workbook { 96 / 72.0 / 12700 // EMU ]; + /// Collections store the font metrics details. + Map _fontMetricsCollection; + + /// Use this NumberFormatChar to check the Unicodes. + final String _numberFormatChar = '€'; + /// Represents zip archive to save the workbook. Archive get archive { _archives ??= Archive(); @@ -146,7 +158,5885 @@ class Workbook { _mergedCellsStyles = value; } + /// Returns the font metrics collections. + Map get _fontMetrics { + if (_fontMetricsCollection == null) _initFontMetricsCollection(); + return _fontMetricsCollection; + } + + /// Returns font height for Calibri and Tahoma. + static Map> _fontHeight; + + /// Returns font height for Calibri and Tahoma. + static Map> get _fontsHeight { + if (_fontHeight == null) { + _initializeFontHeight(); + } + return _fontHeight; + } + + /// Initialize the font height for Calibri and Tahoma fonts. + static void _initializeFontHeight() { + _fontHeight = >{}; + + //Calibri font height; + Map keyValuePair = {}; + keyValuePair[1] = 5.25; + keyValuePair[2] = 5.25; + keyValuePair[3] = 6; + keyValuePair[4] = 6.75; + keyValuePair[5] = 8.25; + keyValuePair[6] = 8.25; + keyValuePair[7] = 9; + keyValuePair[8] = 11.25; + keyValuePair[9] = 12; + keyValuePair[10] = 12.75; + keyValuePair[11] = 15; + keyValuePair[12] = 15.75; + keyValuePair[13] = 17.25; + keyValuePair[14] = 18.75; + keyValuePair[15] = 19.5; + keyValuePair[16] = 21; + keyValuePair[17] = 22.5; + keyValuePair[18] = 23.25; + keyValuePair[19] = 24.75; + keyValuePair[20] = 26.25; + keyValuePair[21] = 27.75; + keyValuePair[22] = 28.5; + keyValuePair[23] = 30; + keyValuePair[24] = 31.5; + keyValuePair[25] = 32.25; + keyValuePair[26] = 33.75; + keyValuePair[27] = 35.25; + keyValuePair[28] = 36; + keyValuePair[29] = 37.5; + keyValuePair[30] = 39; + keyValuePair[31] = 39.75; + keyValuePair[32] = 42; + keyValuePair[33] = 42.75; + keyValuePair[34] = 43.5; + keyValuePair[35] = 45.75; + keyValuePair[36] = 46.5; + keyValuePair[37] = 47.25; + keyValuePair[38] = 49.5; + keyValuePair[39] = 50.25; + keyValuePair[40] = 51; + keyValuePair[41] = 53.25; + keyValuePair[42] = 54; + keyValuePair[43] = 54.75; + keyValuePair[44] = 57; + keyValuePair[45] = 57.75; + keyValuePair[46] = 58.5; + keyValuePair[47] = 60.75; + keyValuePair[48] = 61.5; + keyValuePair[49] = 62.25; + keyValuePair[50] = 64.5; + keyValuePair[51] = 65.25; + keyValuePair[52] = 66.75; + keyValuePair[53] = 68.25; + keyValuePair[54] = 69; + keyValuePair[55] = 70.5; + keyValuePair[56] = 72; + keyValuePair[57] = 72.75; + keyValuePair[58] = 74.25; + keyValuePair[59] = 75.75; + keyValuePair[60] = 76.5; + keyValuePair[61] = 78; + keyValuePair[62] = 79.5; + keyValuePair[63] = 81; + keyValuePair[64] = 81.75; + keyValuePair[65] = 83.25; + keyValuePair[66] = 84.75; + keyValuePair[67] = 85.5; + keyValuePair[68] = 87; + keyValuePair[69] = 88.5; + keyValuePair[70] = 89.25; + keyValuePair[71] = 91.5; + keyValuePair[72] = 92.25; + keyValuePair[73] = 93; + keyValuePair[74] = 95.25; + keyValuePair[75] = 96; + keyValuePair[76] = 96.75; + keyValuePair[77] = 99; + keyValuePair[78] = 99.75; + keyValuePair[79] = 100.5; + keyValuePair[80] = 102.75; + keyValuePair[81] = 103.5; + keyValuePair[82] = 104.25; + keyValuePair[83] = 106.5; + keyValuePair[84] = 107.25; + keyValuePair[85] = 108; + keyValuePair[86] = 110.25; + keyValuePair[87] = 111; + keyValuePair[88] = 111.75; + keyValuePair[89] = 114; + keyValuePair[90] = 114.75; + keyValuePair[91] = 115.5; + keyValuePair[92] = 117.75; + keyValuePair[93] = 118.5; + keyValuePair[94] = 120; + keyValuePair[95] = 121.5; + keyValuePair[96] = 122.25; + keyValuePair[97] = 123.75; + keyValuePair[98] = 125.25; + keyValuePair[99] = 126; + keyValuePair[100] = 127.5; + keyValuePair[101] = 129; + keyValuePair[102] = 130.5; + keyValuePair[103] = 131.25; + keyValuePair[104] = 132.75; + keyValuePair[105] = 134.25; + keyValuePair[106] = 135; + keyValuePair[107] = 136.5; + keyValuePair[108] = 138; + keyValuePair[109] = 138.75; + keyValuePair[110] = 140.25; + keyValuePair[111] = 141.75; + keyValuePair[112] = 142.5; + keyValuePair[113] = 144.75; + keyValuePair[114] = 145.5; + keyValuePair[115] = 146.25; + keyValuePair[116] = 148.5; + keyValuePair[117] = 149.25; + keyValuePair[118] = 150; + keyValuePair[119] = 152.25; + keyValuePair[120] = 153; + keyValuePair[121] = 153.75; + keyValuePair[122] = 156; + keyValuePair[123] = 156.75; + keyValuePair[124] = 157.5; + keyValuePair[125] = 159.75; + keyValuePair[126] = 160.5; + keyValuePair[127] = 161.25; + keyValuePair[128] = 163.5; + keyValuePair[129] = 164.25; + keyValuePair[130] = 165; + keyValuePair[131] = 167.25; + keyValuePair[132] = 168; + keyValuePair[133] = 169.5; + keyValuePair[134] = 171; + keyValuePair[135] = 171.75; + keyValuePair[136] = 173.25; + keyValuePair[137] = 174.75; + keyValuePair[138] = 175.5; + keyValuePair[139] = 177; + keyValuePair[140] = 178.5; + keyValuePair[141] = 179.25; + keyValuePair[142] = 180.75; + keyValuePair[143] = 182.25; + keyValuePair[144] = 183.75; + keyValuePair[145] = 184.5; + keyValuePair[146] = 186; + keyValuePair[147] = 187.5; + keyValuePair[148] = 188.25; + keyValuePair[149] = 189.75; + keyValuePair[150] = 191.25; + keyValuePair[151] = 192; + keyValuePair[152] = 194.25; + keyValuePair[153] = 195; + keyValuePair[154] = 195.75; + keyValuePair[155] = 198; + keyValuePair[156] = 198.75; + keyValuePair[157] = 199.5; + keyValuePair[158] = 201.75; + keyValuePair[159] = 202.5; + keyValuePair[160] = 203.25; + keyValuePair[161] = 205.5; + keyValuePair[162] = 206.25; + keyValuePair[163] = 207; + keyValuePair[164] = 209.25; + keyValuePair[165] = 210; + keyValuePair[166] = 210.75; + keyValuePair[167] = 213; + keyValuePair[168] = 213.75; + keyValuePair[169] = 214.5; + keyValuePair[170] = 216.75; + keyValuePair[171] = 217.5; + keyValuePair[172] = 218.25; + keyValuePair[173] = 220.5; + keyValuePair[174] = 221.25; + keyValuePair[175] = 222.75; + keyValuePair[176] = 224.25; + keyValuePair[177] = 225; + keyValuePair[178] = 226.5; + keyValuePair[179] = 228; + keyValuePair[180] = 228.75; + keyValuePair[181] = 230.25; + keyValuePair[182] = 231.75; + keyValuePair[183] = 233.25; + keyValuePair[184] = 234; + keyValuePair[185] = 235.5; + keyValuePair[186] = 237; + keyValuePair[187] = 237.75; + keyValuePair[188] = 239.25; + keyValuePair[189] = 240.75; + keyValuePair[190] = 241.5; + keyValuePair[191] = 243; + keyValuePair[192] = 244.5; + keyValuePair[193] = 245.25; + keyValuePair[194] = 247.5; + keyValuePair[195] = 248.25; + keyValuePair[196] = 249; + keyValuePair[197] = 251.25; + keyValuePair[198] = 252; + keyValuePair[199] = 252.75; + keyValuePair[200] = 255; + keyValuePair[201] = 255.75; + keyValuePair[202] = 256.5; + keyValuePair[203] = 258.75; + keyValuePair[204] = 259.5; + keyValuePair[205] = 260.25; + keyValuePair[206] = 262.5; + keyValuePair[207] = 263.25; + keyValuePair[208] = 264; + keyValuePair[209] = 266.25; + keyValuePair[210] = 267; + keyValuePair[211] = 267.75; + keyValuePair[212] = 270; + keyValuePair[213] = 270.75; + keyValuePair[214] = 272.25; + keyValuePair[215] = 273.75; + keyValuePair[216] = 274.5; + keyValuePair[217] = 276; + keyValuePair[218] = 277.5; + keyValuePair[219] = 278.25; + keyValuePair[220] = 279.75; + keyValuePair[221] = 281.25; + keyValuePair[222] = 282; + keyValuePair[223] = 283.5; + keyValuePair[224] = 285; + keyValuePair[225] = 286.5; + keyValuePair[226] = 287.25; + keyValuePair[227] = 288.75; + keyValuePair[228] = 290.25; + keyValuePair[229] = 291; + keyValuePair[230] = 292.5; + keyValuePair[231] = 294; + keyValuePair[232] = 294.75; + keyValuePair[233] = 297; + keyValuePair[234] = 297.75; + keyValuePair[235] = 298.5; + keyValuePair[236] = 300.75; + keyValuePair[237] = 301.5; + keyValuePair[238] = 302.25; + keyValuePair[239] = 304.5; + keyValuePair[240] = 305.25; + keyValuePair[241] = 306; + keyValuePair[242] = 308.25; + keyValuePair[243] = 309; + keyValuePair[244] = 309.75; + keyValuePair[245] = 312; + keyValuePair[246] = 312.75; + keyValuePair[247] = 313.5; + keyValuePair[248] = 315.75; + keyValuePair[249] = 316.5; + keyValuePair[250] = 317.25; + keyValuePair[251] = 319.5; + keyValuePair[252] = 320.25; + keyValuePair[253] = 321.75; + keyValuePair[254] = 323.25; + keyValuePair[255] = 324; + keyValuePair[256] = 325.5; + keyValuePair[257] = 327; + keyValuePair[258] = 327.75; + keyValuePair[259] = 329.25; + keyValuePair[260] = 330.75; + keyValuePair[261] = 331.5; + keyValuePair[262] = 333; + keyValuePair[263] = 334.5; + keyValuePair[264] = 336; + keyValuePair[265] = 336.75; + keyValuePair[266] = 338.25; + keyValuePair[267] = 339.75; + keyValuePair[268] = 340.5; + keyValuePair[269] = 342; + keyValuePair[270] = 343.5; + keyValuePair[271] = 344.25; + keyValuePair[272] = 345.75; + keyValuePair[273] = 347.25; + keyValuePair[274] = 348; + keyValuePair[275] = 350.25; + keyValuePair[276] = 351; + keyValuePair[277] = 351.75; + keyValuePair[278] = 354; + keyValuePair[279] = 354.75; + keyValuePair[280] = 355.5; + keyValuePair[281] = 357.75; + keyValuePair[282] = 358.5; + keyValuePair[283] = 359.25; + keyValuePair[284] = 361.5; + keyValuePair[285] = 362.25; + keyValuePair[286] = 363; + keyValuePair[287] = 365.25; + keyValuePair[288] = 366; + keyValuePair[289] = 366.75; + keyValuePair[290] = 369; + keyValuePair[291] = 369.75; + keyValuePair[292] = 370.5; + keyValuePair[293] = 372.75; + keyValuePair[294] = 373.5; + keyValuePair[295] = 375; + keyValuePair[296] = 376.5; + keyValuePair[297] = 377.25; + keyValuePair[298] = 378.75; + keyValuePair[299] = 380.25; + keyValuePair[300] = 381; + keyValuePair[301] = 382.5; + keyValuePair[302] = 384; + keyValuePair[303] = 384.75; + keyValuePair[304] = 386.25; + keyValuePair[305] = 387.75; + keyValuePair[306] = 389.25; + keyValuePair[307] = 390; + keyValuePair[308] = 391.5; + keyValuePair[309] = 393; + keyValuePair[310] = 393.75; + keyValuePair[311] = 395.25; + keyValuePair[312] = 396.75; + keyValuePair[313] = 397.5; + keyValuePair[314] = 399.75; + keyValuePair[315] = 400.5; + keyValuePair[316] = 401.25; + keyValuePair[317] = 403.5; + keyValuePair[318] = 404.25; + keyValuePair[319] = 405; + keyValuePair[320] = 407.25; + keyValuePair[321] = 408; + keyValuePair[322] = 408.75; + keyValuePair[323] = 409.5; + keyValuePair[324] = 409.5; + keyValuePair[325] = 409.5; + keyValuePair[326] = 409.5; + keyValuePair[327] = 409.5; + keyValuePair[328] = 409.5; + keyValuePair[329] = 409.5; + keyValuePair[330] = 409.5; + keyValuePair[331] = 409.5; + keyValuePair[332] = 409.5; + keyValuePair[333] = 409.5; + keyValuePair[334] = 409.5; + keyValuePair[335] = 409.5; + keyValuePair[336] = 409.5; + keyValuePair[337] = 409.5; + keyValuePair[338] = 409.5; + keyValuePair[339] = 409.5; + keyValuePair[340] = 409.5; + keyValuePair[341] = 409.5; + keyValuePair[342] = 409.5; + keyValuePair[343] = 409.5; + keyValuePair[344] = 409.5; + keyValuePair[345] = 409.5; + keyValuePair[346] = 409.5; + keyValuePair[347] = 409.5; + keyValuePair[348] = 409.5; + keyValuePair[349] = 409.5; + keyValuePair[350] = 409.5; + keyValuePair[351] = 409.5; + keyValuePair[352] = 409.5; + keyValuePair[353] = 409.5; + keyValuePair[354] = 409.5; + keyValuePair[355] = 409.5; + keyValuePair[356] = 409.5; + keyValuePair[357] = 409.5; + keyValuePair[358] = 409.5; + keyValuePair[359] = 409.5; + keyValuePair[360] = 409.5; + keyValuePair[361] = 409.5; + keyValuePair[362] = 409.5; + keyValuePair[363] = 409.5; + keyValuePair[364] = 409.5; + keyValuePair[365] = 409.5; + keyValuePair[366] = 409.5; + keyValuePair[367] = 409.5; + keyValuePair[368] = 409.5; + keyValuePair[369] = 409.5; + keyValuePair[370] = 409.5; + keyValuePair[371] = 409.5; + keyValuePair[372] = 409.5; + keyValuePair[373] = 409.5; + keyValuePair[374] = 409.5; + keyValuePair[375] = 409.5; + keyValuePair[376] = 409.5; + keyValuePair[377] = 409.5; + keyValuePair[378] = 409.5; + keyValuePair[379] = 409.5; + keyValuePair[380] = 409.5; + keyValuePair[381] = 409.5; + keyValuePair[382] = 409.5; + keyValuePair[383] = 409.5; + keyValuePair[384] = 409.5; + keyValuePair[385] = 409.5; + keyValuePair[386] = 409.5; + keyValuePair[387] = 409.5; + keyValuePair[388] = 409.5; + keyValuePair[389] = 409.5; + keyValuePair[390] = 409.5; + keyValuePair[391] = 409.5; + keyValuePair[392] = 409.5; + keyValuePair[393] = 409.5; + keyValuePair[394] = 409.5; + keyValuePair[395] = 409.5; + keyValuePair[396] = 409.5; + keyValuePair[397] = 409.5; + keyValuePair[398] = 409.5; + keyValuePair[399] = 409.5; + keyValuePair[400] = 409.5; + keyValuePair[401] = 409.5; + keyValuePair[402] = 409.5; + keyValuePair[403] = 409.5; + keyValuePair[404] = 409.5; + keyValuePair[405] = 409.5; + keyValuePair[406] = 409.5; + keyValuePair[407] = 409.5; + keyValuePair[408] = 409.5; + keyValuePair[409] = 409.5; + _fontHeight['Calibri'] = keyValuePair; + + //Tahoma font height + keyValuePair = {}; + keyValuePair[1] = 5.25; + keyValuePair[2] = 5.25; + keyValuePair[3] = 6; + keyValuePair[4] = 6.75; + keyValuePair[5] = 8.25; + keyValuePair[6] = 8.25; + keyValuePair[7] = 9; + keyValuePair[8] = 10.5; + keyValuePair[9] = 11.25; + keyValuePair[10] = 12.75; + keyValuePair[11] = 14.25; + keyValuePair[12] = 15; + keyValuePair[13] = 16.5; + keyValuePair[14] = 18; + keyValuePair[15] = 18.75; + keyValuePair[16] = 19.5; + keyValuePair[17] = 21.75; + keyValuePair[18] = 22.5; + keyValuePair[19] = 23.25; + keyValuePair[20] = 25.5; + keyValuePair[21] = 26.25; + keyValuePair[22] = 27; + keyValuePair[23] = 28.5; + keyValuePair[24] = 30; + keyValuePair[25] = 30.75; + keyValuePair[26] = 32.25; + keyValuePair[27] = 33; + keyValuePair[28] = 34.5; + keyValuePair[29] = 36; + keyValuePair[30] = 36.75; + keyValuePair[31] = 37.5; + keyValuePair[32] = 39.75; + keyValuePair[33] = 40.5; + keyValuePair[34] = 41.25; + keyValuePair[35] = 43.5; + keyValuePair[36] = 44.25; + keyValuePair[37] = 45; + keyValuePair[38] = 47.25; + keyValuePair[39] = 48; + keyValuePair[40] = 48.75; + keyValuePair[41] = 50.25; + keyValuePair[42] = 51.75; + keyValuePair[43] = 52.5; + keyValuePair[44] = 54; + keyValuePair[45] = 54.75; + keyValuePair[46] = 56.25; + keyValuePair[47] = 57.75; + keyValuePair[48] = 58.5; + keyValuePair[49] = 59.25; + keyValuePair[50] = 61.5; + keyValuePair[51] = 62.25; + keyValuePair[52] = 63; + keyValuePair[53] = 65.25; + keyValuePair[54] = 66; + keyValuePair[55] = 66.75; + keyValuePair[56] = 68.25; + keyValuePair[57] = 69.75; + keyValuePair[58] = 70.5; + keyValuePair[59] = 72; + keyValuePair[60] = 73.5; + keyValuePair[61] = 74.25; + keyValuePair[62] = 75.75; + keyValuePair[63] = 76.5; + keyValuePair[64] = 78; + keyValuePair[65] = 79.5; + keyValuePair[66] = 80.25; + keyValuePair[67] = 81; + keyValuePair[68] = 83.25; + keyValuePair[69] = 84; + keyValuePair[70] = 84.75; + keyValuePair[71] = 87; + keyValuePair[72] = 87.75; + keyValuePair[73] = 88.5; + keyValuePair[74] = 90; + keyValuePair[75] = 91.5; + keyValuePair[76] = 92.25; + keyValuePair[77] = 93.75; + keyValuePair[78] = 94.5; + keyValuePair[79] = 96; + keyValuePair[80] = 97.5; + keyValuePair[81] = 98.25; + keyValuePair[82] = 99.75; + keyValuePair[83] = 101.25; + keyValuePair[84] = 102; + keyValuePair[85] = 102.75; + keyValuePair[86] = 105; + keyValuePair[87] = 105.75; + keyValuePair[88] = 106.5; + keyValuePair[89] = 108.75; + keyValuePair[90] = 109.5; + keyValuePair[91] = 110.25; + keyValuePair[92] = 111.75; + keyValuePair[93] = 113.25; + keyValuePair[94] = 114; + keyValuePair[95] = 115.5; + keyValuePair[96] = 116.25; + keyValuePair[97] = 117.75; + keyValuePair[98] = 119.25; + keyValuePair[99] = 120; + keyValuePair[100] = 120.75; + keyValuePair[101] = 123; + keyValuePair[102] = 123.75; + keyValuePair[103] = 124.5; + keyValuePair[104] = 126.75; + keyValuePair[105] = 127.5; + keyValuePair[106] = 128.25; + keyValuePair[107] = 130.5; + keyValuePair[108] = 131.25; + keyValuePair[109] = 132; + keyValuePair[110] = 133.5; + keyValuePair[111] = 135; + keyValuePair[112] = 135.75; + keyValuePair[113] = 137.25; + keyValuePair[114] = 138; + keyValuePair[115] = 139.5; + keyValuePair[116] = 141; + keyValuePair[117] = 141.75; + keyValuePair[118] = 142.5; + keyValuePair[119] = 144.75; + keyValuePair[120] = 145.5; + keyValuePair[121] = 146.25; + keyValuePair[122] = 148.5; + keyValuePair[123] = 149.25; + keyValuePair[124] = 150; + keyValuePair[125] = 151.5; + keyValuePair[126] = 153; + keyValuePair[127] = 153.75; + keyValuePair[128] = 155.25; + keyValuePair[129] = 156.75; + keyValuePair[130] = 157.5; + keyValuePair[131] = 159; + keyValuePair[132] = 159.75; + keyValuePair[133] = 161.25; + keyValuePair[134] = 162.75; + keyValuePair[135] = 163.5; + keyValuePair[136] = 164.25; + keyValuePair[137] = 166.5; + keyValuePair[138] = 167.25; + keyValuePair[139] = 168; + keyValuePair[140] = 170.25; + keyValuePair[141] = 171; + keyValuePair[142] = 171.75; + keyValuePair[143] = 173.25; + keyValuePair[144] = 174.75; + keyValuePair[145] = 175.5; + keyValuePair[146] = 177; + keyValuePair[147] = 177.75; + keyValuePair[148] = 179.25; + keyValuePair[149] = 180.75; + keyValuePair[150] = 181.5; + keyValuePair[151] = 183; + keyValuePair[152] = 184.5; + keyValuePair[153] = 185.25; + keyValuePair[154] = 186; + keyValuePair[155] = 188.25; + keyValuePair[156] = 189; + keyValuePair[157] = 189.75; + keyValuePair[158] = 192; + keyValuePair[159] = 192.75; + keyValuePair[160] = 193.5; + keyValuePair[161] = 195; + keyValuePair[162] = 196.5; + keyValuePair[163] = 197.25; + keyValuePair[164] = 198.75; + keyValuePair[165] = 199.5; + keyValuePair[166] = 201; + keyValuePair[167] = 202.5; + keyValuePair[168] = 203.25; + keyValuePair[169] = 204; + keyValuePair[170] = 206.25; + keyValuePair[171] = 207; + keyValuePair[172] = 207.75; + keyValuePair[173] = 210; + keyValuePair[174] = 210.75; + keyValuePair[175] = 211.5; + keyValuePair[176] = 213.75; + keyValuePair[177] = 214.5; + keyValuePair[178] = 215.25; + keyValuePair[179] = 216.75; + keyValuePair[180] = 218.25; + keyValuePair[181] = 219; + keyValuePair[182] = 220.5; + keyValuePair[183] = 221.25; + keyValuePair[184] = 222.75; + keyValuePair[185] = 224.25; + keyValuePair[186] = 225; + keyValuePair[187] = 225.75; + keyValuePair[188] = 228; + keyValuePair[189] = 228.75; + keyValuePair[190] = 229.5; + keyValuePair[191] = 231.75; + keyValuePair[192] = 232.5; + keyValuePair[193] = 233.25; + keyValuePair[194] = 234.75; + keyValuePair[195] = 236.25; + keyValuePair[196] = 237; + keyValuePair[197] = 238.5; + keyValuePair[198] = 240; + keyValuePair[199] = 240.75; + keyValuePair[200] = 242.25; + keyValuePair[201] = 243; + keyValuePair[202] = 244.5; + keyValuePair[203] = 246; + keyValuePair[204] = 246.75; + keyValuePair[205] = 247.5; + keyValuePair[206] = 249.75; + keyValuePair[207] = 250.5; + keyValuePair[208] = 251.25; + keyValuePair[209] = 253.5; + keyValuePair[210] = 254.25; + keyValuePair[211] = 255; + keyValuePair[212] = 256.5; + keyValuePair[213] = 258; + keyValuePair[214] = 258.75; + keyValuePair[215] = 260.25; + keyValuePair[216] = 261; + keyValuePair[217] = 262.5; + keyValuePair[218] = 264; + keyValuePair[219] = 264.75; + keyValuePair[220] = 266.25; + keyValuePair[221] = 267.75; + keyValuePair[222] = 268.5; + keyValuePair[223] = 269.25; + keyValuePair[224] = 271.5; + keyValuePair[225] = 272.25; + keyValuePair[226] = 273; + keyValuePair[227] = 275.25; + keyValuePair[228] = 276; + keyValuePair[229] = 276.75; + keyValuePair[230] = 278.25; + keyValuePair[231] = 279.75; + keyValuePair[232] = 280.5; + keyValuePair[233] = 282; + keyValuePair[234] = 282.75; + keyValuePair[235] = 284.25; + keyValuePair[236] = 285.75; + keyValuePair[237] = 286.5; + keyValuePair[238] = 287.25; + keyValuePair[239] = 289.5; + keyValuePair[240] = 290.25; + keyValuePair[241] = 291; + keyValuePair[242] = 293.25; + keyValuePair[243] = 294; + keyValuePair[244] = 294.75; + keyValuePair[245] = 297; + keyValuePair[246] = 297.75; + keyValuePair[247] = 298.5; + keyValuePair[248] = 300; + keyValuePair[249] = 301.5; + keyValuePair[250] = 302.25; + keyValuePair[251] = 303.75; + keyValuePair[252] = 304.5; + keyValuePair[253] = 306; + keyValuePair[254] = 307.5; + keyValuePair[255] = 308.25; + keyValuePair[256] = 309; + keyValuePair[257] = 311.25; + keyValuePair[258] = 312; + keyValuePair[259] = 312.75; + keyValuePair[260] = 315; + keyValuePair[261] = 315.75; + keyValuePair[262] = 316.5; + keyValuePair[263] = 318; + keyValuePair[264] = 319.5; + keyValuePair[265] = 320.25; + keyValuePair[266] = 321.75; + keyValuePair[267] = 323.25; + keyValuePair[268] = 324; + keyValuePair[269] = 325.5; + keyValuePair[270] = 326.25; + keyValuePair[271] = 327.75; + keyValuePair[272] = 329.25; + keyValuePair[273] = 330; + keyValuePair[274] = 330.75; + keyValuePair[275] = 333; + keyValuePair[276] = 333.75; + keyValuePair[277] = 334.5; + keyValuePair[278] = 336.75; + keyValuePair[279] = 337.5; + keyValuePair[280] = 338.25; + keyValuePair[281] = 339.75; + keyValuePair[282] = 341.25; + keyValuePair[283] = 342; + keyValuePair[284] = 343.5; + keyValuePair[285] = 344.25; + keyValuePair[286] = 345.75; + keyValuePair[287] = 347.25; + keyValuePair[288] = 348; + keyValuePair[289] = 349.5; + keyValuePair[290] = 351; + keyValuePair[291] = 351.75; + keyValuePair[292] = 352.5; + keyValuePair[293] = 354.75; + keyValuePair[294] = 355.5; + keyValuePair[295] = 356.25; + keyValuePair[296] = 358.5; + keyValuePair[297] = 359.25; + keyValuePair[298] = 360; + keyValuePair[299] = 361.5; + keyValuePair[300] = 363; + keyValuePair[301] = 363.75; + keyValuePair[302] = 365.25; + keyValuePair[303] = 366; + keyValuePair[304] = 367.5; + keyValuePair[305] = 369; + keyValuePair[306] = 369.75; + keyValuePair[307] = 370.5; + keyValuePair[308] = 372.75; + keyValuePair[309] = 373.5; + keyValuePair[310] = 374.25; + keyValuePair[311] = 376.5; + keyValuePair[312] = 377.25; + keyValuePair[313] = 378; + keyValuePair[314] = 380.25; + keyValuePair[315] = 381; + keyValuePair[316] = 381.75; + keyValuePair[317] = 383.25; + keyValuePair[318] = 384.75; + keyValuePair[319] = 385.5; + keyValuePair[320] = 387; + keyValuePair[321] = 387.75; + keyValuePair[322] = 389.25; + keyValuePair[323] = 390.75; + keyValuePair[324] = 391.5; + keyValuePair[325] = 392.25; + keyValuePair[326] = 394.5; + keyValuePair[327] = 395.25; + keyValuePair[328] = 396; + keyValuePair[329] = 398.25; + keyValuePair[330] = 399; + keyValuePair[331] = 399.75; + keyValuePair[332] = 401.25; + keyValuePair[333] = 402.75; + keyValuePair[334] = 403.5; + keyValuePair[335] = 405; + keyValuePair[336] = 406.5; + keyValuePair[337] = 407.25; + keyValuePair[338] = 408.75; + keyValuePair[339] = 409.5; + keyValuePair[340] = 409.5; + keyValuePair[341] = 409.5; + keyValuePair[342] = 409.5; + keyValuePair[343] = 409.5; + keyValuePair[344] = 409.5; + keyValuePair[345] = 409.5; + keyValuePair[346] = 409.5; + keyValuePair[347] = 409.5; + keyValuePair[348] = 409.5; + keyValuePair[349] = 409.5; + keyValuePair[350] = 409.5; + keyValuePair[351] = 409.5; + keyValuePair[352] = 409.5; + keyValuePair[353] = 409.5; + keyValuePair[354] = 409.5; + keyValuePair[355] = 409.5; + keyValuePair[356] = 409.5; + keyValuePair[357] = 409.5; + keyValuePair[358] = 409.5; + keyValuePair[359] = 409.5; + keyValuePair[360] = 409.5; + keyValuePair[361] = 409.5; + keyValuePair[362] = 409.5; + keyValuePair[363] = 409.5; + keyValuePair[364] = 409.5; + keyValuePair[365] = 409.5; + keyValuePair[366] = 409.5; + keyValuePair[367] = 409.5; + keyValuePair[368] = 409.5; + keyValuePair[369] = 409.5; + keyValuePair[370] = 409.5; + keyValuePair[371] = 409.5; + keyValuePair[372] = 409.5; + keyValuePair[373] = 409.5; + keyValuePair[374] = 409.5; + keyValuePair[375] = 409.5; + keyValuePair[376] = 409.5; + keyValuePair[377] = 409.5; + keyValuePair[378] = 409.5; + keyValuePair[379] = 409.5; + keyValuePair[380] = 409.5; + keyValuePair[381] = 409.5; + keyValuePair[382] = 409.5; + keyValuePair[383] = 409.5; + keyValuePair[384] = 409.5; + keyValuePair[385] = 409.5; + keyValuePair[386] = 409.5; + keyValuePair[387] = 409.5; + keyValuePair[388] = 409.5; + keyValuePair[389] = 409.5; + keyValuePair[390] = 409.5; + keyValuePair[391] = 409.5; + keyValuePair[392] = 409.5; + keyValuePair[393] = 409.5; + keyValuePair[394] = 409.5; + keyValuePair[395] = 409.5; + keyValuePair[396] = 409.5; + keyValuePair[397] = 409.5; + keyValuePair[398] = 409.5; + keyValuePair[399] = 409.5; + keyValuePair[400] = 409.5; + keyValuePair[401] = 409.5; + keyValuePair[402] = 409.5; + keyValuePair[403] = 409.5; + keyValuePair[404] = 409.5; + keyValuePair[405] = 409.5; + keyValuePair[406] = 409.5; + keyValuePair[407] = 409.5; + keyValuePair[408] = 409.5; + keyValuePair[409] = 409.5; + _fontHeight['Tahoma'] = keyValuePair; + + //Arial font height + keyValuePair = {}; + keyValuePair[1] = 5.25; + keyValuePair[2] = 5.25; + keyValuePair[3] = 6; + keyValuePair[4] = 6.75; + keyValuePair[5] = 8.25; + keyValuePair[6] = 8.25; + keyValuePair[7] = 9; + keyValuePair[8] = 11.25; + keyValuePair[9] = 12; + keyValuePair[10] = 12.75; + keyValuePair[11] = 14.25; + keyValuePair[12] = 15; + keyValuePair[13] = 16.5; + keyValuePair[14] = 18; + keyValuePair[15] = 18.75; + keyValuePair[16] = 20.25; + keyValuePair[17] = 21.75; + keyValuePair[18] = 23.25; + keyValuePair[19] = 23.25; + keyValuePair[20] = 25.5; + keyValuePair[21] = 26.25; + keyValuePair[22] = 27; + keyValuePair[23] = 29.25; + keyValuePair[24] = 30; + keyValuePair[25] = 30.75; + keyValuePair[26] = 33; + keyValuePair[27] = 33.75; + keyValuePair[28] = 34.5; + keyValuePair[29] = 36.75; + keyValuePair[30] = 37.5; + keyValuePair[31] = 38.25; + keyValuePair[32] = 40.5; + keyValuePair[33] = 41.25; + keyValuePair[34] = 42; + keyValuePair[35] = 43.5; + keyValuePair[36] = 44.25; + keyValuePair[37] = 45.75; + keyValuePair[38] = 47.25; + keyValuePair[39] = 48.75; + keyValuePair[40] = 49.5; + keyValuePair[41] = 51; + keyValuePair[42] = 52.5; + keyValuePair[43] = 53.25; + keyValuePair[44] = 54.75; + keyValuePair[45] = 55.5; + keyValuePair[46] = 56.25; + keyValuePair[47] = 58.5; + keyValuePair[48] = 59.25; + keyValuePair[49] = 60; + keyValuePair[50] = 62.25; + keyValuePair[51] = 63; + keyValuePair[52] = 63.75; + keyValuePair[53] = 66; + keyValuePair[54] = 66.75; + keyValuePair[55] = 67.5; + keyValuePair[56] = 69; + keyValuePair[57] = 69.75; + keyValuePair[58] = 72.75; + keyValuePair[59] = 74.25; + keyValuePair[60] = 75; + keyValuePair[61] = 76.5; + keyValuePair[62] = 78; + keyValuePair[63] = 79.5; + keyValuePair[64] = 80.25; + keyValuePair[65] = 81.75; + keyValuePair[66] = 83.25; + keyValuePair[67] = 84; + keyValuePair[68] = 85.5; + keyValuePair[69] = 86.25; + keyValuePair[70] = 87; + keyValuePair[71] = 89.25; + keyValuePair[72] = 90; + keyValuePair[73] = 90.75; + keyValuePair[74] = 93; + keyValuePair[75] = 93.75; + keyValuePair[76] = 94.5; + keyValuePair[77] = 96.75; + keyValuePair[78] = 97.5; + keyValuePair[79] = 99; + keyValuePair[80] = 99.75; + keyValuePair[81] = 100.5; + keyValuePair[82] = 102; + keyValuePair[83] = 103.5; + keyValuePair[84] = 105; + keyValuePair[85] = 105.75; + keyValuePair[86] = 107.25; + keyValuePair[87] = 108.75; + keyValuePair[88] = 109.5; + keyValuePair[89] = 111.75; + keyValuePair[90] = 112.5; + keyValuePair[91] = 113.25; + keyValuePair[92] = 114.75; + keyValuePair[93] = 115.5; + keyValuePair[94] = 116.25; + keyValuePair[95] = 118.5; + keyValuePair[96] = 119.25; + keyValuePair[97] = 120; + keyValuePair[98] = 122.25; + keyValuePair[99] = 123; + keyValuePair[100] = 124.5; + keyValuePair[101] = 126; + keyValuePair[102] = 126.75; + keyValuePair[103] = 128.25; + keyValuePair[104] = 130.5; + keyValuePair[105] = 132; + keyValuePair[106] = 132.75; + keyValuePair[107] = 134.25; + keyValuePair[108] = 135.75; + keyValuePair[109] = 136.5; + keyValuePair[110] = 138.75; + keyValuePair[111] = 139.5; + keyValuePair[112] = 140.25; + keyValuePair[113] = 141; + keyValuePair[114] = 142.5; + keyValuePair[115] = 143.25; + keyValuePair[116] = 145.5; + keyValuePair[117] = 146.25; + keyValuePair[118] = 147; + keyValuePair[119] = 148.5; + keyValuePair[120] = 149.25; + keyValuePair[121] = 150.75; + keyValuePair[122] = 152.25; + keyValuePair[123] = 153; + keyValuePair[124] = 154.5; + keyValuePair[125] = 156.75; + keyValuePair[126] = 157.5; + keyValuePair[127] = 158.25; + keyValuePair[128] = 159.75; + keyValuePair[129] = 160.5; + keyValuePair[130] = 161.25; + keyValuePair[131] = 162.75; + keyValuePair[132] = 163.5; + keyValuePair[133] = 165; + keyValuePair[134] = 166.5; + keyValuePair[135] = 168; + keyValuePair[136] = 169.5; + keyValuePair[137] = 171; + keyValuePair[138] = 171.75; + keyValuePair[139] = 172.5; + keyValuePair[140] = 174; + keyValuePair[141] = 175.5; + keyValuePair[142] = 176.25; + keyValuePair[143] = 177.75; + keyValuePair[144] = 178.5; + keyValuePair[145] = 179.25; + keyValuePair[146] = 181.5; + keyValuePair[147] = 182.25; + keyValuePair[148] = 183.75; + keyValuePair[149] = 186.75; + keyValuePair[150] = 188.25; + keyValuePair[151] = 189; + keyValuePair[152] = 190.5; + keyValuePair[153] = 191.25; + keyValuePair[154] = 192; + keyValuePair[155] = 193.5; + keyValuePair[156] = 194.25; + keyValuePair[157] = 195; + keyValuePair[158] = 198; + keyValuePair[159] = 198.75; + keyValuePair[160] = 199.5; + keyValuePair[161] = 201.75; + keyValuePair[162] = 202.5; + keyValuePair[163] = 203.25; + keyValuePair[164] = 204.75; + keyValuePair[165] = 205.5; + keyValuePair[166] = 206.25; + keyValuePair[167] = 208.5; + keyValuePair[168] = 210; + keyValuePair[169] = 210.75; + keyValuePair[170] = 212.25; + keyValuePair[171] = 213; + keyValuePair[172] = 213.75; + keyValuePair[173] = 216; + keyValuePair[174] = 216.75; + keyValuePair[175] = 217.5; + keyValuePair[176] = 219.75; + keyValuePair[177] = 220.5; + keyValuePair[178] = 221.25; + keyValuePair[179] = 223.5; + keyValuePair[180] = 224.25; + keyValuePair[181] = 225; + keyValuePair[182] = 226.5; + keyValuePair[183] = 227.25; + keyValuePair[184] = 228.75; + keyValuePair[185] = 230.25; + keyValuePair[186] = 231.75; + keyValuePair[187] = 232.5; + keyValuePair[188] = 234; + keyValuePair[189] = 234.75; + keyValuePair[190] = 236.25; + keyValuePair[191] = 237; + keyValuePair[192] = 237.75; + keyValuePair[193] = 238.5; + keyValuePair[194] = 240.75; + keyValuePair[195] = 243; + keyValuePair[196] = 243.75; + keyValuePair[197] = 246; + keyValuePair[198] = 246.75; + keyValuePair[199] = 247.5; + keyValuePair[200] = 249.75; + keyValuePair[201] = 250.5; + keyValuePair[202] = 251.25; + keyValuePair[203] = 252.75; + keyValuePair[204] = 254.25; + keyValuePair[205] = 255; + keyValuePair[206] = 256.5; + keyValuePair[207] = 257.25; + keyValuePair[208] = 258.75; + keyValuePair[209] = 260.25; + keyValuePair[210] = 261; + keyValuePair[211] = 262.5; + keyValuePair[212] = 264; + keyValuePair[213] = 264.75; + keyValuePair[214] = 265.5; + keyValuePair[215] = 267.75; + keyValuePair[216] = 268.5; + keyValuePair[217] = 269.25; + keyValuePair[218] = 271.5; + keyValuePair[219] = 272.25; + keyValuePair[220] = 273; + keyValuePair[221] = 275.25; + keyValuePair[222] = 276; + keyValuePair[223] = 276.75; + keyValuePair[224] = 278.25; + keyValuePair[225] = 279.75; + keyValuePair[226] = 280.5; + keyValuePair[227] = 282; + keyValuePair[228] = 282.75; + keyValuePair[229] = 284.25; + keyValuePair[230] = 285.75; + keyValuePair[231] = 286.5; + keyValuePair[232] = 287.25; + keyValuePair[233] = 289.5; + keyValuePair[234] = 290.25; + keyValuePair[235] = 291; + keyValuePair[236] = 293.25; + keyValuePair[237] = 294; + keyValuePair[238] = 294.75; + keyValuePair[239] = 297; + keyValuePair[240] = 297.75; + keyValuePair[241] = 300; + keyValuePair[242] = 301.5; + keyValuePair[243] = 303; + keyValuePair[244] = 303.75; + keyValuePair[245] = 305.25; + keyValuePair[246] = 306.75; + keyValuePair[247] = 307.5; + keyValuePair[248] = 309; + keyValuePair[249] = 309.75; + keyValuePair[250] = 311.25; + keyValuePair[251] = 312.75; + keyValuePair[252] = 313.5; + keyValuePair[253] = 314.25; + keyValuePair[254] = 316.5; + keyValuePair[255] = 317.25; + keyValuePair[256] = 318; + keyValuePair[257] = 320.25; + keyValuePair[258] = 321; + keyValuePair[259] = 321.75; + keyValuePair[260] = 324; + keyValuePair[261] = 324.75; + keyValuePair[262] = 325.5; + keyValuePair[263] = 327; + keyValuePair[264] = 328.5; + keyValuePair[265] = 329.25; + keyValuePair[266] = 330.75; + keyValuePair[267] = 331.5; + keyValuePair[268] = 333; + keyValuePair[269] = 334.5; + keyValuePair[270] = 335.25; + keyValuePair[271] = 336.75; + keyValuePair[272] = 338.25; + keyValuePair[273] = 339; + keyValuePair[274] = 339.75; + keyValuePair[275] = 342; + keyValuePair[276] = 342.75; + keyValuePair[277] = 343.5; + keyValuePair[278] = 345.75; + keyValuePair[279] = 346.5; + keyValuePair[280] = 347.25; + keyValuePair[281] = 348.75; + keyValuePair[282] = 350.25; + keyValuePair[283] = 351; + keyValuePair[284] = 352.5; + keyValuePair[285] = 354; + keyValuePair[286] = 354.75; + keyValuePair[287] = 357.75; + keyValuePair[288] = 358.5; + keyValuePair[289] = 360; + keyValuePair[290] = 361.5; + keyValuePair[291] = 362.25; + keyValuePair[292] = 363; + keyValuePair[293] = 365.25; + keyValuePair[294] = 366; + keyValuePair[295] = 366.75; + keyValuePair[296] = 369; + keyValuePair[297] = 369.75; + keyValuePair[298] = 370.5; + keyValuePair[299] = 372.75; + keyValuePair[300] = 373.5; + keyValuePair[301] = 374.25; + keyValuePair[302] = 375.75; + keyValuePair[303] = 377.25; + keyValuePair[304] = 378; + keyValuePair[305] = 379.5; + keyValuePair[306] = 380.25; + keyValuePair[307] = 381.75; + keyValuePair[308] = 383.25; + keyValuePair[309] = 384; + keyValuePair[310] = 385.5; + keyValuePair[311] = 387; + keyValuePair[312] = 387.75; + keyValuePair[313] = 388.5; + keyValuePair[314] = 390.75; + keyValuePair[315] = 391.5; + keyValuePair[316] = 392.25; + keyValuePair[317] = 394.5; + keyValuePair[318] = 395.25; + keyValuePair[319] = 396; + keyValuePair[320] = 397.5; + keyValuePair[321] = 399; + keyValuePair[322] = 399.75; + keyValuePair[323] = 401.25; + keyValuePair[324] = 402.75; + keyValuePair[325] = 403.5; + keyValuePair[326] = 405; + keyValuePair[327] = 405.75; + keyValuePair[328] = 407.25; + keyValuePair[329] = 408.75; + keyValuePair[330] = 409.5; + keyValuePair[331] = 409.5; + keyValuePair[332] = 409.5; + keyValuePair[333] = 409.5; + keyValuePair[334] = 409.5; + keyValuePair[335] = 409.5; + keyValuePair[336] = 409.5; + keyValuePair[337] = 409.5; + keyValuePair[338] = 409.5; + keyValuePair[339] = 409.5; + keyValuePair[340] = 409.5; + keyValuePair[341] = 409.5; + keyValuePair[342] = 409.5; + keyValuePair[343] = 409.5; + keyValuePair[344] = 409.5; + keyValuePair[345] = 409.5; + keyValuePair[346] = 409.5; + keyValuePair[347] = 409.5; + keyValuePair[348] = 409.5; + keyValuePair[349] = 409.5; + keyValuePair[350] = 409.5; + keyValuePair[351] = 409.5; + keyValuePair[352] = 409.5; + keyValuePair[353] = 409.5; + keyValuePair[354] = 409.5; + keyValuePair[355] = 409.5; + keyValuePair[356] = 409.5; + keyValuePair[357] = 409.5; + keyValuePair[358] = 409.5; + keyValuePair[359] = 409.5; + keyValuePair[360] = 409.5; + keyValuePair[361] = 409.5; + keyValuePair[362] = 409.5; + keyValuePair[363] = 409.5; + keyValuePair[364] = 409.5; + keyValuePair[365] = 409.5; + keyValuePair[366] = 409.5; + keyValuePair[367] = 409.5; + keyValuePair[368] = 409.5; + keyValuePair[369] = 409.5; + keyValuePair[370] = 409.5; + keyValuePair[371] = 409.5; + keyValuePair[372] = 409.5; + keyValuePair[373] = 409.5; + keyValuePair[374] = 409.5; + keyValuePair[375] = 409.5; + keyValuePair[376] = 409.5; + keyValuePair[377] = 409.5; + keyValuePair[378] = 409.5; + keyValuePair[379] = 409.5; + keyValuePair[380] = 409.5; + keyValuePair[381] = 409.5; + keyValuePair[382] = 409.5; + keyValuePair[383] = 409.5; + keyValuePair[384] = 409.5; + keyValuePair[385] = 409.5; + keyValuePair[386] = 409.5; + keyValuePair[387] = 409.5; + keyValuePair[388] = 409.5; + keyValuePair[389] = 409.5; + keyValuePair[390] = 409.5; + keyValuePair[391] = 409.5; + keyValuePair[392] = 409.5; + keyValuePair[393] = 409.5; + keyValuePair[394] = 409.5; + keyValuePair[395] = 409.5; + keyValuePair[396] = 409.5; + keyValuePair[397] = 409.5; + keyValuePair[398] = 409.5; + keyValuePair[399] = 409.5; + keyValuePair[400] = 409.5; + keyValuePair[401] = 409.5; + keyValuePair[402] = 409.5; + keyValuePair[403] = 409.5; + keyValuePair[404] = 409.5; + keyValuePair[405] = 409.5; + keyValuePair[406] = 409.5; + keyValuePair[407] = 409.5; + keyValuePair[408] = 409.5; + keyValuePair[409] = 409.5; + _fontHeight['Arial'] = keyValuePair; + } + + /// Arial widths table. + final List _arialWidthTable = [ + 278, + 278, + 355, + 556, + 556, + 889, + 667, + 191, + 333, + 333, + 389, + 584, + 278, + 333, + 278, + 278, + 556, + 556, + 556, + 556, + 556, + 556, + 556, + 556, + 556, + 556, + 278, + 278, + 584, + 584, + 584, + 556, + 1015, + 667, + 667, + 722, + 722, + 667, + 611, + 778, + 722, + 278, + 500, + 667, + 556, + 833, + 722, + 778, + 667, + 778, + 722, + 667, + 611, + 722, + 667, + 944, + 667, + 667, + 611, + 278, + 278, + 278, + 469, + 556, + 333, + 556, + 556, + 500, + 556, + 556, + 278, + 556, + 556, + 222, + 222, + 500, + 222, + 833, + 556, + 556, + 556, + 556, + 333, + 500, + 278, + 556, + 500, + 722, + 500, + 500, + 500, + 334, + 260, + 334, + 584, + 0, + 556, + 0, + 222, + 556, + 333, + 1000, + 556, + 556, + 333, + 1000, + 667, + 333, + 1000, + 0, + 611, + 0, + 0, + 222, + 222, + 333, + 333, + 350, + 556, + 1000, + 333, + 1000, + 500, + 333, + 944, + 0, + 500, + 667, + 0, + 333, + 556, + 556, + 556, + 556, + 260, + 556, + 333, + 737, + 370, + 556, + 584, + 0, + 737, + 333, + 400, + 584, + 333, + 333, + 333, + 556, + 537, + 278, + 333, + 333, + 365, + 556, + 834, + 834, + 834, + 611, + 667, + 667, + 667, + 667, + 667, + 667, + 1000, + 722, + 667, + 667, + 667, + 667, + 278, + 278, + 278, + 278, + 722, + 722, + 778, + 778, + 778, + 778, + 778, + 584, + 778, + 722, + 722, + 722, + 722, + 667, + 667, + 611, + 556, + 556, + 556, + 556, + 556, + 556, + 889, + 500, + 556, + 556, + 556, + 556, + 278, + 278, + 278, + 278, + 556, + 556, + 556, + 556, + 556, + 556, + 556, + 584, + 611, + 556, + 556, + 556, + 556, + 500, + 556, + 500 + ]; + + /// Arial bold widths table. + final List _arialBoldWidthTable = [ + 278, + 333, + 474, + 556, + 556, + 889, + 722, + 238, + 333, + 333, + 389, + 584, + 278, + 333, + 278, + 278, + 556, + 556, + 556, + 556, + 556, + 556, + 556, + 556, + 556, + 556, + 333, + 333, + 584, + 584, + 584, + 611, + 975, + 722, + 722, + 722, + 722, + 667, + 611, + 778, + 722, + 278, + 556, + 722, + 611, + 833, + 722, + 778, + 667, + 778, + 722, + 667, + 611, + 722, + 667, + 944, + 667, + 667, + 611, + 333, + 278, + 333, + 584, + 556, + 333, + 556, + 611, + 556, + 611, + 556, + 333, + 611, + 611, + 278, + 278, + 556, + 278, + 889, + 611, + 611, + 611, + 611, + 389, + 556, + 333, + 611, + 556, + 778, + 556, + 556, + 500, + 389, + 280, + 389, + 584, + 0, + 556, + 0, + 278, + 556, + 500, + 1000, + 556, + 556, + 333, + 1000, + 667, + 333, + 1000, + 0, + 611, + 0, + 0, + 278, + 278, + 500, + 500, + 350, + 556, + 1000, + 333, + 1000, + 556, + 333, + 944, + 0, + 500, + 667, + 0, + 333, + 556, + 556, + 556, + 556, + 280, + 556, + 333, + 737, + 370, + 556, + 584, + 0, + 737, + 333, + 400, + 584, + 333, + 333, + 333, + 611, + 556, + 278, + 333, + 333, + 365, + 556, + 834, + 834, + 834, + 611, + 722, + 722, + 722, + 722, + 722, + 722, + 1000, + 722, + 667, + 667, + 667, + 667, + 278, + 278, + 278, + 278, + 722, + 722, + 778, + 778, + 778, + 778, + 778, + 584, + 778, + 722, + 722, + 722, + 722, + 667, + 667, + 611, + 556, + 556, + 556, + 556, + 556, + 556, + 889, + 556, + 556, + 556, + 556, + 556, + 278, + 278, + 278, + 278, + 611, + 611, + 611, + 611, + 611, + 611, + 611, + 584, + 611, + 611, + 611, + 611, + 611, + 556, + 611, + 556 + ]; + + /// Fixed width of Courier New Font Family. + static const int _courierWidth = 600; + + /// Times New Roman widths table. + final List _timesRomanWidthTable = [ + 250, + 333, + 408, + 500, + 500, + 833, + 778, + 180, + 333, + 333, + 500, + 564, + 250, + 333, + 250, + 278, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 278, + 278, + 564, + 564, + 564, + 444, + 921, + 722, + 667, + 667, + 722, + 611, + 556, + 722, + 722, + 333, + 389, + 722, + 611, + 889, + 722, + 722, + 556, + 722, + 667, + 556, + 611, + 722, + 722, + 944, + 722, + 722, + 611, + 333, + 278, + 333, + 469, + 500, + 333, + 444, + 500, + 444, + 500, + 444, + 333, + 500, + 500, + 278, + 278, + 500, + 278, + 778, + 500, + 500, + 500, + 500, + 333, + 389, + 278, + 500, + 500, + 722, + 500, + 500, + 444, + 480, + 200, + 480, + 541, + 000, + 500, + 000, + 333, + 500, + 444, + 1000, + 500, + 500, + 333, + 1000, + 556, + 333, + 889, + 0, + 611, + 000, + 000, + 333, + 333, + 444, + 444, + 350, + 500, + 1000, + 333, + 980, + 389, + 333, + 722, + 0, + 444, + 722, + 000, + 333, + 500, + 500, + 500, + 500, + 200, + 500, + 333, + 760, + 276, + 500, + 564, + 0, + 760, + 333, + 400, + 564, + 300, + 300, + 333, + 500, + 453, + 250, + 333, + 300, + 310, + 500, + 750, + 750, + 750, + 444, + 722, + 722, + 722, + 722, + 722, + 722, + 889, + 667, + 611, + 611, + 611, + 611, + 333, + 333, + 333, + 333, + 722, + 722, + 722, + 722, + 722, + 722, + 722, + 564, + 722, + 722, + 722, + 722, + 722, + 722, + 556, + 500, + 444, + 444, + 444, + 444, + 444, + 444, + 667, + 444, + 444, + 444, + 444, + 444, + 278, + 278, + 278, + 278, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 564, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 500 + ]; + + /// Times New Roman bold widths table. + final List _timesRomanBoldWidthTable = [ + 250, + 333, + 555, + 500, + 500, + 1000, + 833, + 278, + 333, + 333, + 500, + 570, + 250, + 333, + 250, + 278, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 333, + 333, + 570, + 570, + 570, + 500, + 930, + 722, + 667, + 722, + 722, + 667, + 611, + 778, + 778, + 389, + 500, + 778, + 667, + 944, + 722, + 778, + 611, + 778, + 722, + 556, + 667, + 722, + 722, + 1000, + 722, + 722, + 667, + 333, + 278, + 333, + 581, + 500, + 333, + 500, + 556, + 444, + 556, + 444, + 333, + 500, + 556, + 278, + 333, + 556, + 278, + 833, + 556, + 500, + 556, + 556, + 444, + 389, + 333, + 556, + 500, + 722, + 500, + 500, + 444, + 394, + 220, + 394, + 520, + 0, + 500, + 0, + 333, + 500, + 500, + 1000, + 500, + 500, + 333, + 1000, + 556, + 333, + 1000, + 0, + 667, + 0, + 0, + 333, + 333, + 500, + 500, + 350, + 500, + 1000, + 333, + 1000, + 389, + 333, + 722, + 0, + 444, + 722, + 0, + 333, + 500, + 500, + 500, + 500, + 220, + 500, + 333, + 747, + 300, + 500, + 570, + 0, + 747, + 333, + 400, + 570, + 300, + 300, + 333, + 556, + 540, + 250, + 333, + 300, + 330, + 500, + 750, + 750, + 750, + 500, + 722, + 722, + 722, + 722, + 722, + 722, + 1000, + 722, + 667, + 667, + 667, + 667, + 389, + 389, + 389, + 389, + 722, + 722, + 778, + 778, + 778, + 778, + 778, + 570, + 778, + 722, + 722, + 722, + 722, + 722, + 611, + 556, + 500, + 500, + 500, + 500, + 500, + 500, + 722, + 444, + 444, + 444, + 444, + 444, + 278, + 278, + 278, + 278, + 500, + 556, + 500, + 500, + 500, + 500, + 500, + 570, + 500, + 556, + 556, + 556, + 556, + 500, + 556, + 500 + ]; + + /// Times New Roman italic widths table. + final List _timesRomanItalicWidthTable = [ + 250, + 333, + 420, + 500, + 500, + 833, + 778, + 214, + 333, + 333, + 500, + 675, + 250, + 333, + 250, + 278, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 333, + 333, + 675, + 675, + 675, + 500, + 920, + 611, + 611, + 667, + 722, + 611, + 611, + 722, + 722, + 333, + 444, + 667, + 556, + 833, + 667, + 722, + 611, + 722, + 611, + 500, + 556, + 722, + 611, + 833, + 611, + 556, + 556, + 389, + 278, + 389, + 422, + 500, + 333, + 500, + 500, + 444, + 500, + 444, + 278, + 500, + 500, + 278, + 278, + 444, + 278, + 722, + 500, + 500, + 500, + 500, + 389, + 389, + 278, + 500, + 444, + 667, + 444, + 444, + 389, + 400, + 275, + 400, + 541, + 0, + 500, + 0, + 333, + 500, + 556, + 889, + 500, + 500, + 333, + 1000, + 500, + 333, + 944, + 0, + 556, + 0, + 0, + 333, + 333, + 556, + 556, + 350, + 500, + 889, + 333, + 980, + 389, + 333, + 667, + 0, + 389, + 556, + 0, + 389, + 500, + 500, + 500, + 500, + 275, + 500, + 333, + 760, + 276, + 500, + 675, + 0, + 760, + 333, + 400, + 675, + 300, + 300, + 333, + 500, + 523, + 250, + 333, + 300, + 310, + 500, + 750, + 750, + 750, + 500, + 611, + 611, + 611, + 611, + 611, + 611, + 889, + 667, + 611, + 611, + 611, + 611, + 333, + 333, + 333, + 333, + 722, + 667, + 722, + 722, + 722, + 722, + 722, + 675, + 722, + 722, + 722, + 722, + 722, + 556, + 611, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 667, + 444, + 444, + 444, + 444, + 444, + 278, + 278, + 278, + 278, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 675, + 500, + 500, + 500, + 500, + 500, + 444, + 500, + 444 + ]; + + /// Times New Roman bold italic widths table. + final List _timesRomanBoldItalicWidthTable = [ + 250, + 389, + 555, + 500, + 500, + 833, + 778, + 278, + 333, + 333, + 500, + 570, + 250, + 333, + 250, + 278, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 333, + 333, + 570, + 570, + 570, + 500, + 832, + 667, + 667, + 667, + 722, + 667, + 667, + 722, + 778, + 389, + 500, + 667, + 611, + 889, + 722, + 722, + 611, + 722, + 667, + 556, + 611, + 722, + 667, + 889, + 667, + 611, + 611, + 333, + 278, + 333, + 570, + 500, + 333, + 500, + 500, + 444, + 500, + 444, + 333, + 500, + 556, + 278, + 278, + 500, + 278, + 778, + 556, + 500, + 500, + 500, + 389, + 389, + 278, + 556, + 444, + 667, + 500, + 444, + 389, + 348, + 220, + 348, + 570, + 0, + 500, + 0, + 333, + 500, + 500, + 1000, + 500, + 500, + 333, + 1000, + 556, + 333, + 944, + 0, + 611, + 0, + 0, + 333, + 333, + 500, + 500, + 350, + 500, + 1000, + 333, + 1000, + 389, + 333, + 722, + 0, + 389, + 611, + 0, + 389, + 500, + 500, + 500, + 500, + 220, + 500, + 333, + 747, + 266, + 500, + 606, + 0, + 747, + 333, + 400, + 570, + 300, + 300, + 333, + 576, + 500, + 250, + 333, + 300, + 300, + 500, + 750, + 750, + 750, + 500, + 667, + 667, + 667, + 667, + 667, + 667, + 944, + 667, + 667, + 667, + 667, + 667, + 389, + 389, + 389, + 389, + 722, + 722, + 722, + 722, + 722, + 722, + 722, + 570, + 722, + 722, + 722, + 722, + 722, + 611, + 611, + 500, + 500, + 500, + 500, + 500, + 500, + 500, + 722, + 444, + 444, + 444, + 444, + 444, + 278, + 278, + 278, + 278, + 500, + 556, + 500, + 500, + 500, + 500, + 500, + 570, + 500, + 556, + 556, + 556, + 556, + 444, + 500, + 444 + ]; + + /// Tahoma widths table. + final List _tahomaWidthTable = [ + 312, + 332, + 401, + 727, + 545, + 976, + 673, + 210, + 382, + 382, + 545, + 727, + 302, + 363, + 302, + 382, + 545, + 545, + 545, + 545, + 545, + 545, + 545, + 545, + 545, + 545, + 353, + 353, + 727, + 727, + 727, + 473, + 909, + 599, + 589, + 600, + 678, + 561, + 521, + 667, + 675, + 373, + 416, + 587, + 497, + 770, + 667, + 707, + 551, + 707, + 620, + 557, + 583, + 655, + 596, + 901, + 580, + 576, + 559, + 382, + 382, + 382, + 727, + 545, + 545, + 524, + 552, + 461, + 552, + 526, + 318, + 552, + 557, + 228, + 281, + 498, + 228, + 839, + 557, + 542, + 552, + 552, + 360, + 446, + 334, + 557, + 498, + 742, + 495, + 498, + 444, + 480, + 382, + 480, + 727, + 312, + 332, + 545, + 545, + 545, + 545, + 382, + 545, + 545, + 928, + 493, + 573, + 727, + 363, + 928, + 545, + 470, + 727, + 493, + 493, + 545, + 567, + 545, + 353, + 545, + 493, + 493, + 573, + 1000, + 1000, + 1000, + 473, + 599, + 599, + 599, + 599, + 599, + 599, + 913, + 600, + 561, + 561, + 561, + 561, + 373, + 373, + 373, + 373, + 698, + 667, + 707, + 707, + 707, + 707, + 707, + 727, + 707, + 655, + 655, + 655, + 655, + 576, + 565, + 548, + 524, + 524, + 524, + 524, + 524, + 524, + 879, + 461, + 526, + 526, + 526, + 526, + 228, + 228, + 228, + 228, + 545, + 557, + 542, + 542, + 542, + 542, + 542, + 727, + 542, + 557, + 557, + 557, + 557, + 498, + 552, + 498, + 599, + 524, + 599, + 524, + 599, + 524, + 600, + 461, + 600, + 461, + 600, + 461, + 600, + 461, + 678, + 687, + 698, + 573, + 561, + 526, + 561, + 526, + 561, + 526, + 561, + 526, + 561, + 526, + 667, + 552, + 667, + 552, + 667, + 552, + 667, + 552, + 675, + 557, + 715, + 578, + 373, + 228, + 373, + 228, + 373, + 228, + 373, + 228, + 373, + 228, + 730, + 515, + 416, + 281, + 587, + 498, + 498, + 497, + 228, + 497, + 228, + 497, + 360, + 497, + 445, + 517, + 274, + 667, + 557, + 667, + 557, + 667, + 557, + 692, + 667, + 557, + 707, + 542, + 707, + 542, + 707, + 542, + 976, + 908, + 620, + 360, + 620, + 360, + 620, + 360, + 557, + 446, + 557, + 446, + 557, + 446, + 557, + 446, + 583, + 334, + 583, + 468, + 583, + 339, + 655, + 557, + 655, + 557, + 655, + 557, + 655, + 557, + 655, + 557, + 655, + 557, + 901, + 742, + 576, + 498, + 576, + 559, + 444, + 559, + 444, + 559, + 444 + ]; + + /// Tahoma Bold widths table. + final List _tahomaBoldWidthTable = [ + 292, + 342, + 489, + 818, + 636, + 1198, + 781, + 275, + 454, + 454, + 636, + 818, + 312, + 431, + 312, + 577, + 636, + 636, + 636, + 636, + 636, + 636, + 636, + 636, + 636, + 636, + 363, + 363, + 818, + 818, + 818, + 566, + 919, + 684, + 686, + 667, + 757, + 615, + 581, + 745, + 764, + 483, + 500, + 696, + 572, + 893, + 770, + 770, + 657, + 770, + 726, + 633, + 612, + 738, + 674, + 1027, + 684, + 670, + 622, + 454, + 577, + 454, + 818, + 636, + 545, + 598, + 631, + 527, + 629, + 593, + 382, + 629, + 640, + 301, + 362, + 602, + 301, + 953, + 640, + 617, + 629, + 629, + 433, + 514, + 415, + 640, + 578, + 889, + 604, + 575, + 525, + 623, + 636, + 623, + 818, + 292, + 342, + 636, + 636, + 636, + 636, + 636, + 636, + 545, + 928, + 507, + 703, + 818, + 431, + 928, + 636, + 519, + 818, + 539, + 539, + 545, + 650, + 636, + 363, + 545, + 539, + 539, + 703, + 1127, + 1127, + 1127, + 566, + 684, + 684, + 684, + 684, + 684, + 684, + 988, + 667, + 615, + 615, + 615, + 615, + 483, + 483, + 483, + 483, + 773, + 770, + 770, + 770, + 770, + 770, + 770, + 818, + 770, + 738, + 738, + 738, + 738, + 670, + 659, + 645, + 598, + 598, + 598, + 598, + 598, + 598, + 937, + 527, + 593, + 593, + 593, + 593, + 301, + 301, + 301, + 301, + 619, + 640, + 617, + 617, + 617, + 617, + 617, + 818, + 617, + 640, + 640, + 640, + 640, + 575, + 629, + 575, + 684, + 598, + 684, + 598, + 684, + 598, + 667, + 527, + 667, + 527, + 667, + 527, + 667, + 527, + 757, + 817, + 773, + 625, + 615, + 593, + 615, + 593, + 615, + 593, + 615, + 593, + 615, + 593, + 745, + 629, + 745, + 629, + 745, + 629, + 745, + 629, + 764, + 640, + 781, + 635, + 483, + 301, + 483, + 301, + 483, + 301, + 483, + 301, + 483, + 301, + 939, + 647, + 500, + 362, + 696, + 602, + 602, + 572, + 301, + 572, + 301, + 572, + 489, + 572, + 487, + 588, + 334, + 770, + 640, + 770, + 640, + 770, + 640, + 742, + 770, + 640, + 770, + 617, + 770, + 617, + 770, + 617, + 1036, + 985, + 726, + 433, + 726, + 433, + 726, + 433, + 633, + 514, + 633, + 514, + 633, + 514, + 633, + 514, + 612, + 415, + 612, + 619, + 612, + 415, + 738, + 640, + 738, + 640, + 738, + 640, + 738, + 640, + 738, + 640, + 738, + 640, + 1027, + 889, + 670, + 575, + 670, + 622, + 525, + 622, + 525, + 622, + 525 + ]; + + /// Calibri Width Table. + final List _calibriWidthTable = [ + 226, + 325, + 400, + 498, + 506, + 714, + 682, + 220, + 303, + 303, + 498, + 498, + 249, + 306, + 252, + 386, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 267, + 267, + 498, + 498, + 498, + 463, + 894, + 578, + 543, + 533, + 615, + 488, + 459, + 630, + 623, + 251, + 318, + 519, + 420, + 854, + 645, + 662, + 516, + 672, + 542, + 459, + 487, + 641, + 567, + 889, + 519, + 487, + 468, + 306, + 386, + 306, + 498, + 498, + 291, + 479, + 525, + 422, + 525, + 497, + 305, + 470, + 525, + 229, + 239, + 454, + 229, + 798, + 525, + 527, + 525, + 525, + 348, + 391, + 334, + 525, + 451, + 714, + 433, + 452, + 395, + 314, + 460, + 314, + 498, + 226, + 325, + 498, + 506, + 498, + 506, + 498, + 498, + 392, + 834, + 402, + 512, + 498, + 306, + 506, + 394, + 338, + 498, + 335, + 334, + 291, + 549, + 585, + 252, + 307, + 246, + 422, + 512, + 636, + 671, + 675, + 463, + 578, + 578, + 578, + 578, + 578, + 578, + 763, + 533, + 488, + 488, + 488, + 488, + 251, + 251, + 251, + 251, + 624, + 645, + 662, + 662, + 662, + 662, + 662, + 498, + 663, + 641, + 641, + 641, + 641, + 487, + 516, + 527, + 479, + 479, + 479, + 479, + 479, + 479, + 772, + 422, + 497, + 497, + 497, + 497, + 229, + 229, + 229, + 229, + 525, + 525, + 527, + 527, + 527, + 527, + 527, + 498, + 529, + 525, + 525, + 525, + 525, + 452, + 525, + 452, + 578, + 479, + 578, + 479, + 578, + 479, + 533, + 422, + 533, + 422, + 533, + 422, + 533, + 422, + 615, + 568, + 624, + 551, + 488, + 497, + 488, + 497, + 488, + 497, + 488, + 497, + 488, + 497, + 630, + 470, + 630, + 470, + 630, + 470, + 630, + 470, + 623, + 525, + 656, + 532, + 251, + 229, + 251, + 229, + 251, + 229, + 251, + 229, + 251, + 229, + 571, + 468, + 318, + 239, + 519, + 454, + 454, + 420, + 229, + 420, + 229, + 422, + 263, + 545, + 373, + 429, + 247, + 645, + 525, + 645, + 525, + 645, + 525, + 579, + 628, + 525, + 662, + 527, + 662, + 527, + 662, + 527, + 866, + 849, + 542, + 348, + 542, + 348, + 542, + 348, + 459, + 391, + 459, + 391, + 459, + 391, + 459, + 391, + 487, + 334, + 487, + 345, + 487, + 341, + 641, + 525, + 641, + 525, + 641, + 525, + 641, + 525, + 641, + 525, + 641, + 525, + 889, + 714, + 487, + 452, + 487, + 468, + 395, + 468, + 395, + 468, + 395 + ]; + + /// Calibri Bold Width Table. + final List _calibriBoldWidthTable = [ + 226, + 325, + 438, + 498, + 506, + 729, + 704, + 233, + 311, + 311, + 498, + 498, + 257, + 306, + 267, + 429, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 275, + 275, + 498, + 498, + 498, + 463, + 898, + 605, + 560, + 529, + 630, + 487, + 458, + 637, + 630, + 266, + 331, + 546, + 422, + 874, + 658, + 676, + 532, + 686, + 562, + 472, + 495, + 652, + 591, + 906, + 550, + 519, + 478, + 324, + 429, + 324, + 498, + 498, + 300, + 493, + 536, + 418, + 536, + 503, + 316, + 474, + 536, + 245, + 255, + 479, + 245, + 813, + 536, + 537, + 536, + 536, + 355, + 398, + 346, + 536, + 473, + 745, + 459, + 473, + 397, + 343, + 475, + 343, + 498, + 226, + 325, + 498, + 506, + 498, + 506, + 498, + 498, + 414, + 834, + 416, + 538, + 498, + 306, + 506, + 390, + 342, + 498, + 337, + 335, + 300, + 563, + 597, + 267, + 303, + 252, + 435, + 538, + 657, + 690, + 701, + 463, + 605, + 605, + 605, + 605, + 605, + 605, + 775, + 529, + 487, + 487, + 487, + 487, + 266, + 266, + 266, + 266, + 639, + 658, + 676, + 676, + 676, + 676, + 676, + 498, + 680, + 652, + 652, + 652, + 652, + 519, + 532, + 554, + 493, + 493, + 493, + 493, + 493, + 493, + 774, + 418, + 503, + 503, + 503, + 503, + 245, + 245, + 245, + 245, + 536, + 536, + 537, + 537, + 537, + 537, + 537, + 498, + 543, + 536, + 536, + 536, + 536, + 473, + 536, + 473, + 605, + 493, + 605, + 493, + 605, + 493, + 529, + 418, + 529, + 418, + 529, + 418, + 529, + 418, + 630, + 596, + 639, + 568, + 487, + 503, + 487, + 503, + 487, + 503, + 487, + 503, + 487, + 503, + 637, + 474, + 637, + 474, + 637, + 474, + 637, + 474, + 630, + 536, + 657, + 547, + 266, + 245, + 266, + 245, + 266, + 245, + 266, + 245, + 266, + 245, + 598, + 501, + 331, + 255, + 546, + 479, + 479, + 422, + 245, + 422, + 245, + 430, + 306, + 561, + 422, + 432, + 263, + 658, + 536, + 658, + 536, + 658, + 536, + 622, + 641, + 536, + 676, + 537, + 676, + 537, + 676, + 537, + 874, + 842, + 562, + 355, + 562, + 355, + 562, + 355, + 472, + 398, + 472, + 398, + 472, + 398, + 472, + 398, + 495, + 346, + 495, + 363, + 495, + 354, + 652, + 536, + 652, + 536, + 652, + 536, + 652, + 536, + 652, + 536, + 652, + 536, + 906, + 745, + 519, + 473, + 519, + 478, + 397, + 478, + 397, + 478, + 397 + ]; + + /// Calibri Italic Width Table. + final List _calibriItalicWidthTable = [ + 226, + 325, + 400, + 498, + 506, + 714, + 682, + 220, + 303, + 303, + 498, + 498, + 249, + 306, + 252, + 387, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 267, + 267, + 498, + 498, + 498, + 463, + 894, + 578, + 543, + 522, + 615, + 488, + 459, + 630, + 623, + 251, + 318, + 519, + 420, + 854, + 644, + 654, + 516, + 664, + 542, + 452, + 487, + 641, + 567, + 890, + 519, + 487, + 468, + 306, + 384, + 306, + 498, + 498, + 291, + 514, + 514, + 416, + 514, + 477, + 305, + 514, + 514, + 229, + 239, + 454, + 229, + 791, + 514, + 513, + 514, + 514, + 342, + 389, + 334, + 514, + 445, + 714, + 433, + 447, + 395, + 314, + 460, + 314, + 498, + 226, + 325, + 498, + 506, + 498, + 506, + 498, + 498, + 392, + 834, + 430, + 512, + 498, + 306, + 506, + 394, + 338, + 498, + 335, + 334, + 291, + 538, + 585, + 252, + 307, + 246, + 422, + 512, + 636, + 671, + 675, + 463, + 578, + 578, + 578, + 578, + 578, + 578, + 763, + 522, + 488, + 488, + 488, + 488, + 251, + 251, + 251, + 251, + 624, + 644, + 654, + 654, + 654, + 654, + 654, + 498, + 657, + 641, + 641, + 641, + 641, + 487, + 516, + 527, + 514, + 514, + 514, + 514, + 514, + 514, + 754, + 416, + 477, + 477, + 477, + 477, + 229, + 229, + 229, + 229, + 525, + 514, + 513, + 513, + 513, + 513, + 513, + 498, + 529, + 514, + 514, + 514, + 514, + 447, + 514, + 447, + 578, + 514, + 578, + 514, + 578, + 514, + 522, + 416, + 522, + 416, + 522, + 416, + 522, + 416, + 615, + 554, + 624, + 550, + 488, + 477, + 488, + 477, + 488, + 477, + 488, + 477, + 488, + 477, + 630, + 514, + 630, + 514, + 630, + 514, + 630, + 514, + 623, + 514, + 656, + 520, + 251, + 229, + 251, + 229, + 251, + 229, + 251, + 229, + 251, + 229, + 571, + 468, + 318, + 239, + 519, + 454, + 454, + 420, + 229, + 420, + 229, + 422, + 263, + 545, + 373, + 429, + 247, + 644, + 514, + 644, + 514, + 644, + 514, + 568, + 626, + 514, + 654, + 513, + 654, + 513, + 654, + 513, + 866, + 814, + 542, + 342, + 542, + 342, + 542, + 342, + 452, + 389, + 452, + 389, + 452, + 389, + 452, + 389, + 487, + 334, + 487, + 345, + 487, + 341, + 641, + 514, + 641, + 514, + 641, + 514, + 641, + 514, + 641, + 514, + 641, + 514, + 890, + 714, + 487, + 447, + 487, + 468, + 395, + 468, + 395, + 468, + 395 + ]; + + /// Calibri Bold Italic Width Table. + final List _calibriBoldItalicWidthTable = [ + 226, + 325, + 438, + 498, + 506, + 729, + 704, + 233, + 311, + 311, + 498, + 498, + 257, + 306, + 267, + 434, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 506, + 275, + 275, + 498, + 498, + 498, + 463, + 898, + 605, + 560, + 518, + 630, + 487, + 458, + 637, + 630, + 266, + 331, + 546, + 422, + 874, + 656, + 668, + 532, + 677, + 562, + 465, + 495, + 652, + 591, + 906, + 550, + 519, + 478, + 324, + 424, + 324, + 498, + 498, + 300, + 527, + 527, + 411, + 527, + 491, + 316, + 527, + 527, + 245, + 255, + 479, + 245, + 803, + 527, + 527, + 527, + 527, + 352, + 394, + 346, + 527, + 469, + 745, + 459, + 470, + 397, + 343, + 475, + 343, + 498, + 226, + 325, + 498, + 506, + 498, + 506, + 498, + 498, + 414, + 834, + 440, + 538, + 498, + 306, + 506, + 390, + 342, + 498, + 337, + 335, + 300, + 553, + 597, + 267, + 303, + 252, + 435, + 538, + 657, + 690, + 701, + 463, + 605, + 605, + 605, + 605, + 605, + 605, + 775, + 518, + 487, + 487, + 487, + 487, + 266, + 266, + 266, + 266, + 639, + 656, + 668, + 668, + 668, + 668, + 668, + 498, + 677, + 652, + 652, + 652, + 652, + 519, + 532, + 554, + 527, + 527, + 527, + 527, + 527, + 527, + 763, + 411, + 491, + 491, + 491, + 491, + 245, + 245, + 245, + 245, + 536, + 527, + 527, + 527, + 527, + 527, + 527, + 498, + 543, + 527, + 527, + 527, + 527, + 470, + 527, + 470, + 605, + 527, + 605, + 527, + 605, + 527, + 518, + 411, + 518, + 411, + 518, + 411, + 518, + 411, + 630, + 588, + 639, + 566, + 487, + 491, + 487, + 491, + 487, + 491, + 487, + 491, + 487, + 491, + 637, + 527, + 637, + 527, + 637, + 527, + 637, + 527, + 630, + 527, + 657, + 536, + 266, + 245, + 266, + 245, + 266, + 245, + 266, + 245, + 266, + 245, + 598, + 501, + 331, + 255, + 546, + 479, + 479, + 422, + 245, + 422, + 245, + 430, + 306, + 561, + 422, + 432, + 263, + 656, + 527, + 656, + 527, + 656, + 527, + 615, + 637, + 527, + 668, + 527, + 668, + 527, + 668, + 527, + 874, + 816, + 562, + 352, + 562, + 352, + 562, + 352, + 465, + 394, + 465, + 394, + 465, + 394, + 465, + 394, + 495, + 346, + 495, + 363, + 495, + 354, + 652, + 527, + 652, + 527, + 652, + 527, + 652, + 527, + 652, + 527, + 652, + 527, + 906, + 745, + 519, + 470, + 519, + 478, + 397, + 478, + 397, + 478, + 397 + ]; + + /// Verdana Widths Table. + final List _verdanaWidthTable = [ + 351, + 393, + 458, + 818, + 635, + 1076, + 726, + 268, + 454, + 454, + 635, + 818, + 363, + 454, + 363, + 454, + 635, + 635, + 635, + 635, + 635, + 635, + 635, + 635, + 635, + 635, + 454, + 454, + 818, + 818, + 818, + 545, + 1000, + 683, + 685, + 698, + 770, + 632, + 574, + 775, + 751, + 420, + 454, + 692, + 556, + 842, + 748, + 787, + 603, + 787, + 695, + 683, + 616, + 731, + 683, + 988, + 685, + 615, + 685, + 454, + 454, + 454, + 818, + 635, + 635, + 600, + 623, + 520, + 623, + 595, + 351, + 623, + 632, + 274, + 344, + 591, + 274, + 972, + 632, + 606, + 623, + 623, + 426, + 520, + 394, + 632, + 591, + 818, + 591, + 591, + 525, + 634, + 454, + 634, + 818, + 351, + 393, + 635, + 635, + 635, + 635, + 454, + 635, + 635, + 1000, + 545, + 644, + 818, + 454, + 1000, + 635, + 541, + 818, + 541, + 541, + 635, + 641, + 635, + 363, + 635, + 541, + 545, + 644, + 1000, + 1000, + 1000, + 545, + 683, + 683, + 683, + 683, + 683, + 683, + 984, + 698, + 632, + 632, + 632, + 632, + 420, + 420, + 420, + 420, + 775, + 748, + 787, + 787, + 787, + 787, + 787, + 818, + 787, + 731, + 731, + 731, + 731, + 615, + 605, + 620, + 600, + 600, + 600, + 600, + 600, + 600, + 955, + 520, + 595, + 595, + 595, + 595, + 274, + 274, + 274, + 274, + 611, + 632, + 606, + 606, + 606, + 606, + 606, + 818, + 606, + 632, + 632, + 632, + 632, + 591, + 623, + 591, + 683, + 600, + 683, + 600, + 683, + 600, + 698, + 520, + 698, + 520, + 698, + 520, + 698, + 520, + 770, + 647, + 775, + 623, + 632, + 595, + 632, + 595, + 632, + 595, + 632, + 595, + 632, + 595, + 775, + 623, + 775, + 623, + 775, + 623, + 775, + 623, + 751, + 632, + 751, + 632, + 420, + 274, + 420, + 274, + 420, + 274, + 420, + 274, + 420, + 274, + 870, + 613, + 454, + 344, + 692, + 591, + 591, + 556, + 274, + 556, + 274, + 556, + 295, + 556, + 458, + 561, + 284, + 748, + 632, + 748, + 632, + 748, + 632, + 730, + 748, + 632, + 787, + 606, + 787, + 606, + 787, + 606, + 1069, + 981, + 695, + 426, + 695, + 426, + 695, + 426, + 683, + 520, + 683, + 520, + 683, + 520, + 683, + 520, + 616, + 394, + 616, + 394, + 616, + 394, + 731, + 632, + 731, + 632, + 731, + 632, + 731, + 632, + 731, + 632, + 731, + 630, + 988, + 818, + 615, + 591, + 615, + 685, + 525, + 685, + 525, + 685, + 525 + ]; + + /// Verdana Italic widths table + final List _verdanaItalicWidthTable = [ + 351, + 393, + 458, + 818, + 635, + 1076, + 726, + 268, + 454, + 454, + 635, + 818, + 363, + 454, + 363, + 454, + 635, + 635, + 635, + 635, + 635, + 635, + 635, + 635, + 635, + 635, + 454, + 454, + 818, + 818, + 818, + 545, + 1000, + 682, + 685, + 698, + 765, + 632, + 574, + 775, + 751, + 420, + 454, + 692, + 556, + 842, + 748, + 787, + 603, + 787, + 695, + 683, + 616, + 731, + 682, + 990, + 685, + 615, + 685, + 454, + 454, + 454, + 818, + 635, + 635, + 600, + 623, + 520, + 623, + 595, + 351, + 621, + 632, + 274, + 344, + 586, + 274, + 973, + 632, + 606, + 623, + 623, + 426, + 520, + 394, + 632, + 590, + 818, + 591, + 590, + 525, + 634, + 454, + 634, + 818, + 351, + 393, + 635, + 635, + 635, + 635, + 454, + 635, + 635, + 1000, + 545, + 644, + 818, + 454, + 1000, + 635, + 541, + 818, + 541, + 541, + 635, + 641, + 635, + 363, + 635, + 541, + 545, + 644, + 1000, + 1000, + 1000, + 545, + 682, + 682, + 682, + 682, + 682, + 682, + 989, + 698, + 632, + 632, + 632, + 632, + 420, + 420, + 420, + 420, + 765, + 748, + 787, + 787, + 787, + 787, + 787, + 818, + 787, + 731, + 731, + 731, + 731, + 615, + 605, + 620, + 600, + 600, + 600, + 600, + 600, + 600, + 954, + 520, + 595, + 595, + 595, + 595, + 274, + 274, + 274, + 274, + 611, + 632, + 606, + 606, + 606, + 606, + 606, + 818, + 606, + 632, + 632, + 632, + 632, + 590, + 623, + 590, + 682, + 600, + 682, + 600, + 682, + 600, + 698, + 520, + 698, + 520, + 698, + 520, + 698, + 520, + 765, + 647, + 765, + 623, + 632, + 595, + 632, + 595, + 632, + 595, + 632, + 595, + 632, + 595, + 775, + 621, + 775, + 621, + 775, + 621, + 775, + 621, + 751, + 632, + 751, + 632, + 420, + 274, + 420, + 274, + 420, + 274, + 420, + 274, + 420, + 274, + 870, + 613, + 454, + 344, + 692, + 586, + 586, + 556, + 274, + 556, + 274, + 556, + 295, + 556, + 458, + 556, + 274, + 748, + 632, + 748, + 632, + 748, + 632, + 730, + 748, + 632, + 787, + 606, + 787, + 606, + 787, + 606, + 1069, + 980, + 695, + 426, + 695, + 426, + 695, + 426, + 683, + 520, + 683, + 520, + 683, + 520, + 683, + 520, + 616, + 394, + 616, + 394, + 616, + 394, + 731, + 632, + 731, + 632, + 731, + 632, + 731, + 632, + 731, + 632, + 731, + 632, + 990, + 818, + 615, + 590, + 615, + 685, + 525, + 685, + 525, + 685, + 525 + ]; + + /// Verdana Bold Width Table. + final List _verdanaBoldWidthTable = [ + 341, + 402, + 587, + 867, + 710, + 1271, + 862, + 332, + 543, + 543, + 710, + 867, + 361, + 479, + 361, + 689, + 710, + 710, + 710, + 710, + 710, + 710, + 710, + 710, + 710, + 710, + 402, + 402, + 867, + 867, + 867, + 616, + 963, + 776, + 761, + 723, + 830, + 683, + 650, + 811, + 837, + 545, + 555, + 770, + 637, + 947, + 846, + 850, + 732, + 850, + 782, + 710, + 681, + 812, + 763, + 1128, + 763, + 736, + 691, + 543, + 689, + 543, + 867, + 710, + 710, + 667, + 699, + 588, + 699, + 664, + 422, + 699, + 712, + 341, + 402, + 670, + 341, + 1058, + 712, + 686, + 699, + 699, + 497, + 593, + 455, + 712, + 649, + 979, + 668, + 650, + 596, + 710, + 543, + 710, + 867, + 341, + 402, + 710, + 710, + 710, + 710, + 543, + 710, + 710, + 963, + 597, + 849, + 867, + 479, + 963, + 710, + 587, + 867, + 597, + 597, + 710, + 721, + 710, + 361, + 710, + 597, + 597, + 849, + 1181, + 1181, + 1181, + 616, + 776, + 776, + 776, + 776, + 776, + 776, + 1093, + 723, + 683, + 683, + 683, + 683, + 545, + 545, + 545, + 545, + 830, + 846, + 850, + 850, + 850, + 850, + 850, + 867, + 850, + 812, + 812, + 812, + 812, + 736, + 734, + 712, + 667, + 667, + 667, + 667, + 667, + 667, + 1018, + 588, + 664, + 664, + 664, + 664, + 341, + 341, + 341, + 341, + 679, + 712, + 686, + 686, + 686, + 686, + 686, + 867, + 686, + 712, + 712, + 712, + 712, + 650, + 699, + 650, + 776, + 667, + 776, + 667, + 776, + 667, + 723, + 588, + 723, + 588, + 723, + 588, + 723, + 588, + 830, + 879, + 830, + 699, + 683, + 664, + 683, + 664, + 683, + 664, + 683, + 664, + 683, + 664, + 811, + 699, + 811, + 699, + 811, + 699, + 811, + 699, + 837, + 712, + 837, + 712, + 545, + 341, + 545, + 341, + 545, + 341, + 545, + 341, + 545, + 341, + 1007, + 727, + 555, + 402, + 770, + 670, + 670, + 637, + 341, + 637, + 341, + 637, + 522, + 637, + 556, + 642, + 351, + 846, + 712, + 846, + 712, + 846, + 712, + 825, + 846, + 712, + 850, + 686, + 850, + 686, + 850, + 686, + 1135, + 1067, + 782, + 497, + 782, + 497, + 782, + 497, + 710, + 593, + 710, + 593, + 710, + 593, + 710, + 593, + 681, + 455, + 681, + 465, + 681, + 455, + 812, + 712, + 812, + 712, + 812, + 712, + 812, + 712, + 812, + 712, + 812, + 712, + 1128, + 979, + 736, + 650, + 736, + 691, + 596, + 691, + 596, + 691, + 596 + ]; + + /// Verdana Bold Italics Widths table + final List _verdanaBoldItalicWidthTable = [ + 341, + 402, + 587, + 867, + 710, + 1271, + 862, + 332, + 543, + 543, + 710, + 867, + 361, + 479, + 361, + 689, + 710, + 710, + 710, + 710, + 710, + 710, + 710, + 710, + 710, + 710, + 402, + 402, + 867, + 867, + 867, + 616, + 963, + 776, + 761, + 723, + 830, + 683, + 650, + 811, + 837, + 545, + 555, + 770, + 637, + 947, + 846, + 850, + 732, + 850, + 782, + 710, + 681, + 812, + 763, + 1128, + 763, + 736, + 691, + 543, + 689, + 543, + 867, + 710, + 710, + 667, + 699, + 588, + 699, + 664, + 422, + 699, + 712, + 341, + 402, + 670, + 341, + 1058, + 712, + 685, + 699, + 699, + 497, + 593, + 455, + 712, + 648, + 979, + 668, + 650, + 596, + 710, + 543, + 710, + 867, + 341, + 402, + 710, + 710, + 710, + 710, + 543, + 710, + 710, + 963, + 597, + 849, + 867, + 479, + 963, + 710, + 587, + 867, + 597, + 597, + 710, + 721, + 710, + 361, + 710, + 597, + 597, + 849, + 1181, + 1181, + 1181, + 616, + 776, + 776, + 776, + 776, + 776, + 776, + 1093, + 723, + 683, + 683, + 683, + 683, + 545, + 545, + 545, + 545, + 830, + 846, + 850, + 850, + 850, + 850, + 850, + 867, + 850, + 812, + 812, + 812, + 812, + 736, + 734, + 712, + 667, + 667, + 667, + 667, + 667, + 667, + 1018, + 588, + 664, + 664, + 664, + 664, + 341, + 341, + 341, + 341, + 679, + 712, + 685, + 685, + 685, + 685, + 685, + 867, + 685, + 712, + 712, + 712, + 712, + 650, + 699, + 650, + 776, + 667, + 776, + 667, + 776, + 667, + 723, + 588, + 723, + 588, + 723, + 588, + 723, + 588, + 830, + 879, + 830, + 699, + 683, + 664, + 683, + 664, + 683, + 664, + 683, + 664, + 683, + 664, + 811, + 699, + 811, + 699, + 811, + 699, + 811, + 699, + 837, + 712, + 837, + 712, + 545, + 341, + 545, + 341, + 545, + 341, + 545, + 341, + 545, + 341, + 1007, + 727, + 555, + 402, + 770, + 670, + 670, + 637, + 341, + 637, + 341, + 637, + 522, + 637, + 556, + 637, + 351, + 846, + 712, + 846, + 712, + 846, + 712, + 825, + 846, + 712, + 850, + 685, + 850, + 685, + 850, + 685, + 1135, + 1067, + 782, + 497, + 782, + 497, + 782, + 497, + 710, + 593, + 710, + 593, + 710, + 593, + 710, + 593, + 681, + 455, + 681, + 465, + 681, + 455, + 812, + 712, + 812, + 712, + 812, + 712, + 812, + 712, + 812, + 712, + 812, + 712, + 1128, + 979, + 736, + 650, + 736, + 691, + 596, + 691, + 596, + 691, + 596 + ]; + + /// Gets or sets the standard font size. + double get _standardFontSize { + return fonts[0].size; + } + + /// Gets or sets the standard font name. + String get _standardFont { + return fonts[0].name; + } + + /// Represents the Carriage Return character. + static const String _carriageReturn = '\r'; + + /// Represents the Carriage new line character. + static const String _newLine = '\n'; + + static const String _newLineConstant = '\r\n'; + /// Represents the worksheet collection. + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// List bytes = workbook.saveAsStream(); + /// File('Worksheets.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` WorksheetCollection get worksheets { _worksheets ??= WorksheetCollection(this); return _worksheets; @@ -183,6 +6073,28 @@ class Workbook { return _rawFormats; } + /// Represents the hyperlink collection. + HyperlinkCollection get hyperlink { + _hyperlink ??= HyperlinkCollection(); + return _hyperlink; + } + + set hyperlink(HyperlinkCollection value) { + _hyperlink = value; + } + + /// True if cells are protected. + bool _bCellProtect = false; + + /// True if window is protected. + bool _bWindowProtect = false; + + /// 16-bit hash value of the password. + int _isPassword; + + /// Workbook Password. + String _password; + /// Initialize the workbook. void _initializeWorkbook( String givenCulture, String givenCurrency, int count) { @@ -223,22 +6135,15 @@ class Workbook { chartCount = 0; } - /// Saves workbook with the specified file name. - void save(String fileName) { - if (fileName == null || fileName == '') { - throw Exception('fileName should not be null or empty'); - } - _saving = true; - final SerializeWorkbook serializer = SerializeWorkbook(this); - serializer._saveInternal(); - final bytes = ZipEncoder().encode(archive); - - File(fileName).writeAsBytes(bytes); - _saving = false; - } - /// Saves workbook as stream. - List saveStream() { + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// List bytes = workbook.saveAsStream(); + /// File('ExcelSave.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + List saveAsStream() { _saving = true; final SerializeWorkbook serializer = SerializeWorkbook(this); serializer._saveInternal(); @@ -309,6 +6214,434 @@ class Workbook { return _cultureInfo; } + /// Truncate the double value + static double _truncate(double value) { + double result = value.roundToDouble(); + if (result > value) result -= 1; + return result; + } + + /// Converts column width in characters into column width in file. + double _widthToFileWidth(double width) { + final double dDigitWidth = _dMaxDigitWidth; + return (width > 1) + ? ((width * dDigitWidth + 5) / dDigitWidth * 256.0) / 256.0 + : (width * (dDigitWidth + 5) / dDigitWidth * 256.0) / 256.0; + } + + /// Convert column width that is stored in file into pixels. + double _fileWidthToPixels(double fileWidth) { + final double dDigitWidth = _dMaxDigitWidth; + return _truncate( + ((256 * fileWidth + _truncate(128 / dDigitWidth)) / 256) * dDigitWidth); + } + + /// Converts column width in pixels into column width in characters. + double _pixelsToWidth(int pixels) { + final double dDigitWidth = _dMaxDigitWidth; + return (pixels > dDigitWidth + 5) + ? _truncate((pixels - 5) / dDigitWidth * 100 + 0.5) / 100 + : pixels / (dDigitWidth + 5); + } + + /// Converts to pixels. + double _convertToPixels(double value, int from) { + return value * _unitsProportions[from]; + } + + /// Converts from pixel. + double _convertFromPixel(double value, int to) { + return value / _unitsProportions[to]; + } + + /// Converts units. + double _convertUnits(double value, int from, int to) { + return (from == to) + ? value + : value * _unitsProportions[from] / _unitsProportions[to]; + } + + /// Initialize the Font metrics collection for the fonts. + void _initFontMetricsCollection() { + _fontMetricsCollection = {}; + //Arial + _fontMetricsCollection['arial_italic_bold'] = + _FontMetrics(962, -228, 0, 962 + 228.toDouble(), 1.52, 1.52); + + _fontMetricsCollection['arial_bold'] = + _FontMetrics(962, -228, 0, 962 + 228.toDouble(), 1.52, 1.52); + + _fontMetricsCollection['arial_italic'] = + _FontMetrics(931, -225, 0, 931 + 225.toDouble(), 1.52, 1.52); + + _fontMetricsCollection['arial'] = + _FontMetrics(931, -225, 0, 931 + 225.toDouble(), 1.52, 1.52); + + //Times Roman + _fontMetricsCollection['times_italic_bold'] = + _FontMetrics(921, -218, 0, 921 + 218.toDouble(), 1.52, 1.52); + + _fontMetricsCollection['times_bold'] = + _FontMetrics(935, -218, 0, 935 + 218.toDouble(), 1.52, 1.52); + + _fontMetricsCollection['times_italic'] = + _FontMetrics(883, -217, 0, 883 + 217.toDouble(), 1.52, 1.52); + + _fontMetricsCollection['times'] = + _FontMetrics(898, -218, 0, 898 + 218.toDouble(), 1.52, 1.52); + + //Courier + _fontMetricsCollection['courier_italic_bold'] = + _FontMetrics(801, -250, 0, 801 + 250.toDouble(), 1.52, 1.52); + + _fontMetricsCollection['courier_bold'] = + _FontMetrics(801, -250, 0, 801 + 250.toDouble(), 1.52, 1.52); + + _fontMetricsCollection['courier_italic'] = + _FontMetrics(805, -250, 0, 805 + 250.toDouble(), 1.52, 1.52); + + _fontMetricsCollection['courier'] = + _FontMetrics(805, -250, 0, 805 + 250.toDouble(), 1.52, 1.52); + + //Tahoma + _fontMetricsCollection['tahoma'] = _FontMetrics( + 1000.48828, -206.542969, 0, 1207.03125, 1.53869271, 1.53869271); + + //Calibri + _fontMetricsCollection['calibri'] = + _FontMetrics(750.0, -250.0, 221, 1221.0, 1.53869271, 1.53869271); + + //Verdana + _fontMetricsCollection['verdana'] = _FontMetrics( + 1005.37109, -209.960938, 0, 1215.332, 1.53869271, 1.53869271); + } + + /// returns the size of the text with font family 'Verdana'. + _SizeF _getVerdanaTextSize(String text, Font font) { + _FontMetrics fontMetrics; + double height = 0; + double width = 0; + fontMetrics = _fontMetrics['verdana']; + height = _convertToPixels(fontMetrics._getHeight(font), 6); + if (font.bold && font.italic) { + width = _getTotalWidthOfText(text, _verdanaBoldItalicWidthTable, false); + } else if (font.italic) { + width = _getTotalWidthOfText(text, _verdanaItalicWidthTable, false); + } else if (font.bold) { + width = _getTotalWidthOfText(text, _verdanaBoldWidthTable, false); + } else { + width = _getTotalWidthOfText(text, _verdanaWidthTable, false); + } + width = (width * 0.001 * font.size); + width = _convertToPixels(width, 6); + return _SizeF(width, height); + } + + /// returns the size of the text with font family 'Calibri'. + _SizeF _getCalibriTextSize(String text, Font font) { + _FontMetrics fontMetrics; + double height = 0; + double width = 0; + fontMetrics = _fontMetrics['calibri']; + height = _convertToPixels(fontMetrics._getHeight(font), 6); + if (font.bold && font.italic) { + width = _getTotalWidthOfText(text, _calibriBoldItalicWidthTable, false); + } else if (font.italic) { + width = _getTotalWidthOfText(text, _calibriItalicWidthTable, false); + } else if (font.bold) { + width = _getTotalWidthOfText(text, _calibriBoldWidthTable, false); + } else { + width = _getTotalWidthOfText(text, _calibriWidthTable, false); + } + //1.02f value multiplied where measure String and measure character ranges method differs + width = (width * 0.001 * font.size * 1.02); + width = _convertToPixels(width, 6); + return _SizeF(width, height); + } + + /// returns the size of the text with font family 'Tahoma'. + _SizeF _getTahomaTextSize(String text, Font font) { + _FontMetrics fontMetrics; + double height = 0; + double width = 0; + fontMetrics = _fontMetrics['tahoma']; + height = _convertToPixels(fontMetrics._getHeight(font), 6); + if (font.bold && font.italic) { + width = _getTotalWidthOfText(text, _tahomaBoldWidthTable, false) * 1.02; + } else if (font.italic) { + width = _getTotalWidthOfText(text, _tahomaWidthTable, false) * 1.02; + } else if (font.bold) { + width = _getTotalWidthOfText(text, _tahomaBoldWidthTable, false); + } else { + width = _getTotalWidthOfText(text, _tahomaWidthTable, false); + } + width = (width * 0.001 * font.size); + width = _convertToPixels(width, 6); + return _SizeF(width, height); + } + + /// returns the size of the text with font family 'Courier New'. + _SizeF _getCourierTextSize(String text, Font font) { + _FontMetrics fontMetrics; + double height = 0; + double width = 0; + if (font.bold && font.italic) { + fontMetrics = _fontMetrics['courier_italic_bold']; + } else if (font.italic) { + fontMetrics = _fontMetrics['courier_italic']; + } else if (font.bold) { + fontMetrics = _fontMetrics['courier_bold']; + } else { + fontMetrics = _fontMetrics['courier']; + } + height = _convertToPixels(fontMetrics._getHeight(font), 6); + width = _convertToPixels(text.length * _courierWidth * 1.03, 6); + width = (width * 0.001 * font.size); + return _SizeF(width, height); + } + + /// returns the size of the text with font family 'Times New Roman'. + _SizeF _getTimesNewRomanTextSize(String text, Font font) { + _FontMetrics fontMetrics; + double height = 0; + double width = 0; + if (font.bold && font.italic) { + fontMetrics = _fontMetrics['times_italic_bold']; + width = _getTotalWidthOfText(text, _timesRomanBoldItalicWidthTable, true); + } else if (font.italic) { + fontMetrics = _fontMetrics['times_italic']; + width = _getTotalWidthOfText(text, _timesRomanItalicWidthTable, true); + } else if (font.bold) { + fontMetrics = _fontMetrics['times_bold']; + width = _getTotalWidthOfText(text, _timesRomanBoldWidthTable, true); + } else { + fontMetrics = _fontMetrics['times']; + width = _getTotalWidthOfText(text, _timesRomanWidthTable, true); + } + height = _convertToPixels(fontMetrics._getHeight(font), 6); + width = _convertToPixels(width, 6) * 1.02; + width = (width * 0.001 * font.size); + return _SizeF(width, height); + } + + /// returns the size of the text with font family 'Arial'. + _SizeF _getArialTextSize(String text, Font font) { + _FontMetrics fontMetrics; + double height = 0; + double width = 0; + if (font.bold && font.italic) { + fontMetrics = _fontMetrics['arial_italic_bold']; + width = _getTotalWidthOfText(text, _arialBoldWidthTable, true) * 1.02; + } else if (font.italic) { + fontMetrics = _fontMetrics['arial_italic']; + width = _getTotalWidthOfText(text, _arialWidthTable, true) * 1.02; + } else if (font.bold) { + fontMetrics = _fontMetrics['arial_bold']; + width = _getTotalWidthOfText(text, _arialBoldWidthTable, true); + } else { + fontMetrics = _fontMetrics['arial']; + width = _getTotalWidthOfText(text, _arialWidthTable, true); + } + while (text.endsWith('\n')) { + text = text.substring(0, text.length - '\n'.length); + } + final int _newLineCount = + text.length - text.replaceAll('\n', '').length + 1; + height = _convertToPixels(fontMetrics._getHeight(font), 6); + width = _convertToPixels(width, 6); + width = (width * 0.001 * font.size); + return _SizeF(width, height * _newLineCount); + } + + /// True if the text is an unicode else false will returned. + bool _checkUnicode(String unicodeText) { + for (int i = 0; i < unicodeText.length; i++) { + if (unicodeText.codeUnitAt(i) > 255 && + unicodeText[i] != _numberFormatChar) { + return true; + } + } + + return false; + } + + /// Get the total width of string from the given width table. + double _getTotalWidthOfText(String text, List table, bool isStandard) { + double width = 0; + int aIndex; + final bool isUnicode = _checkUnicode(text); + if (isUnicode) { + aIndex = 'M'.codeUnitAt(0); + } else { + aIndex = 'a'.codeUnitAt(0); + } + for (int i = 0; i < text.length; i++) { + int code = text.codeUnitAt(i); + if (isStandard) { + code -= 32; + code = (code >= 0 && code != 128) ? code : 0; + if (code < table.length) { + width += table[code]; + } else { + width += table[aIndex - 32]; + } + } else { + //From unicode char index 32 to 126 + if (code >= 32 && code <= 126) { + width += table[code - 32]; + } + //From unicode char index 160 to 382 + else if (code >= 160 && code <= 382) { + width += table[code - 65]; + } + //If the symbol is not there, default width 'a' is taken + else { + width += table[aIndex - 32]; + } + } + } + return width; + } + + /// Measures the specified string in special way (as close as possible to MS Excel). + _SizeF _measureStringSpecial(String strValue, Font font) { + final _SizeF result = _measureString(strValue, font); + + double originalHeight = result._height; + final Map keyValuePairs = _fontsHeight[font.name]; + double fontHeight; + if (keyValuePairs != null && keyValuePairs[font.size] != null) { + fontHeight = keyValuePairs[font.size]; + originalHeight = _convertUnits(fontHeight, 6, 5) * + ((strValue.length - strValue.replaceAll('\n', '').length) + 1); + } + return _SizeF(result._width, originalHeight); + } + + /// Measures the specified string when drawn with this font. + _SizeF _measureString(String strValue, Font font) { + final Rectangle rectF = Rectangle(0, 0, 1800, 100); + final Rectangle rect = _getMeasuredRectangle(strValue, font, rectF); + return _SizeF(rect.width.toDouble(), rect.height.toDouble()); + } + + /// Measure the Text with a given font. + Rectangle _getMeasuredRectangle(String text, Font font, Rectangle bounds) { + final _SizeF size = _getTextSizeFromFont(text, font); + final double height = (size._height * 1.03).ceilToDouble(); + final int length = bounds.width == 1800.0 + ? 1 + : _getLengthOfLines(size._width, bounds.width, text, font); + final Rectangle result = + Rectangle(0, 0, (size._width).ceil(), height * length); + return result; + } + + /// Measure the text with the input font and returns the size. + _SizeF _getTextSizeFromFont(String text, Font font) { + _SizeF size = _SizeF._empty; + switch (font.name.toLowerCase()) { + case 'arial': + size = _getArialTextSize(text, font); + break; + case 'times new roman': + size = _getTimesNewRomanTextSize(text, font); + break; + case 'courier new': + size = _getCourierTextSize(text, font); + break; + case 'tahoma': + size = _getTahomaTextSize(text, font); + break; + case 'verdana': + size = _getVerdanaTextSize(text, font); + break; + case 'calibri': + default: + size = _getCalibriTextSize(text, font); + break; + } + return size; + } + + /// Calculate the number of lines for the text to fit in the width. + int _getLengthOfLines( + double sizeOfText, double widthBound, String text, Font font) { + int length = 0; + double width = 0; + double currentWidth = 0; + final double spaceWidth = 14; + for (int i = 0; i < text.length; i++) { + currentWidth = (_getTextSizeFromFont(text[i].toString(), font)._width) + .ceilToDouble(); + if ((text[i] == _carriageReturn && + i < text.length - 1 && + text[i + 1] == _newLine) || + (text[i] == _newLine && i < text.length - 1 && text[i + 1] == '0')) { + length += 1; + width = 0; + currentWidth = 0; + i++; + } else if (width + currentWidth + spaceWidth >= widthBound) { + length += 1; + width = currentWidth; + } else if (text[i] == ' ') { + if (i + 2 < text.length && + text.substring(i + 1, i + 3) == _newLineConstant) { + width = 0; + length += 1; + i = i + 2; + } else { + int nextCharIndex = text.indexOf(' ', i + 1); + if (nextCharIndex == -1 && !text.contains(_newLineConstant, i + 1)) { + nextCharIndex = text.length; + } + if (nextCharIndex > i) { + final String subStr = text.substring(i + 1, nextCharIndex); + final double subStrWidth = + (_getTextSizeFromFont(subStr, font)._width).ceilToDouble(); + if (width + currentWidth + subStrWidth + spaceWidth < widthBound) { + width = width + currentWidth + subStrWidth; + i = nextCharIndex - 1; + } else { + length += 1; + width = 0; + currentWidth = 0; + } + } else { + width += currentWidth; + } + } + } else { + width += currentWidth; + } + if (i == text.length - 1 && width > 0) { + length += 1; + } + } + return length == 0 ? 1 : length; + } + + /// Protect workbook using the password from moving, hiding, adding and renaming the worksheet. + void protect(bool isProtectWindow, bool isProtectContent, [String password]) { + if (!isProtectWindow && !isProtectContent) { + throw Exception('One of params must be TRUE.'); + } + if (_bCellProtect || _bWindowProtect) { + throw Exception( + 'Workbook is already protected. Use Unprotect before calling method.'); + } + _bCellProtect = isProtectContent; + _bWindowProtect = isProtectWindow; + + if (password != null) { + _password = password; + final int value = + (_password.isNotEmpty) ? Worksheet._getPasswordHash(_password) : 0; + _isPassword = value; + } + } + /// Dispose objects. void dispose() { if (_archives != null) { diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink.dart new file mode 100644 index 000000000..80735064e --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink.dart @@ -0,0 +1,51 @@ +part of xlsio; + +/// Represent the Hyperlink object. +class Hyperlink { + /// Creates an instance of Hyperlink. + Hyperlink(Worksheet sheet) { + _worksheet = sheet; + } + + /// Represents hyperlink id. + int _rId; + + /// Represents tooltip String. + String screenTip; + + /// Represents the hyperlink location. + // ignore: unused_field + String _location; + + /// Returns or sets the text to be displayed for the specified hyperlink. + /// The default value is the address of the hyperlink. + String textToDisplay; + + /// Represents hyperlink target String. + String address; + + /// Returns or sets the hyperlink type. + HyperlinkType type; + + /// Parent worksheet. + // ignore: unused_field + Worksheet _worksheet; + + /// Row index + int _row; + + /// Column Index + int _column; + + /// Represents the Hyperlink built in style. + // ignore: unused_field + BuiltInStyles _bHyperlinkStyle; + + /// Represents the cell reference name. + String get reference { + return Range._getCellName(_row, _column); + } + + /// Represent the hyperlink applied object name. + ExcelHyperlinkAttachedType _attachedType; +} diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink_collection.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink_collection.dart new file mode 100644 index 000000000..c9c66eec1 --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink_collection.dart @@ -0,0 +1,67 @@ +part of xlsio; + +/// Represents Worksheet Hyperlink collection. +class HyperlinkCollection { + /// Create a instances of Hyperlink collection. + HyperlinkCollection([Worksheet worksheet]) { + _worksheet = worksheet; + _hyperlink = []; + } + + /// Represent the parent worksheet. + Worksheet _worksheet; + + List _hyperlink; + + /// Gets the inner list. + List get innerList { + return _hyperlink; + } + + /// Hyperlink cell index. + Hyperlink operator [](index) => _hyperlink[index]; + + /// Returns the count of hyperlink reference collection. + int get count { + return _hyperlink.length; + } + + /// Add hyperlink to the hyperlink collection. + Hyperlink add(Range range, HyperlinkType linkType, String address, + [String screenTip, String textToDisplay]) { + final Hyperlink hyperlink = Hyperlink(_worksheet); + hyperlink._bHyperlinkStyle = range.builtInStyle = BuiltInStyles.hyperlink; + hyperlink._row = range.row; + hyperlink._column = range.column; + hyperlink.type = linkType; + hyperlink.address = address; + if (screenTip != null) hyperlink.screenTip = screenTip; + if (textToDisplay != null) hyperlink.textToDisplay = textToDisplay; + hyperlink._attachedType = ExcelHyperlinkAttachedType.range; + addHyperlink(hyperlink); + return hyperlink; + } + + /// Add Image hyperlink to the hyperlink collection. + Hyperlink addImage(Picture picture, HyperlinkType linkType, String address, + [String screenTip]) { + if (picture == null) throw Exception('Picture'); + final Hyperlink hyperlink = Hyperlink(_worksheet); + hyperlink.type = linkType; + hyperlink.address = address; + if (screenTip != null) hyperlink.screenTip = screenTip; + hyperlink._attachedType = ExcelHyperlinkAttachedType.shape; + picture._isHyperlink = true; + picture._link = hyperlink; + addHyperlink(hyperlink); + return hyperlink; + } + + /// Add hyperlink to the hyperlinks collection. + void addHyperlink(Hyperlink hyperlink) { + if (hyperlink != null) { + innerList.add(hyperlink); + hyperlink._rId = count; + } + } +} diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/images/picture.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/images/picture.dart index 0ead89ba6..9ef5dcae7 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/images/picture.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/images/picture.dart @@ -20,30 +20,129 @@ class Picture { List _imageData; /// Gets/Sets the image row. + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// List bytes = File('image.png').readAsBytesSync(); + /// Picture picture = sheet.picutes.addStream(1, 1, bytes); + /// picture.row = 2; + /// List bytes = workbook.saveAsStream(); + /// File('Picutes.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` int row; /// Gets/Sets the image column. + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// List bytes = File('image.png').readAsBytesSync(); + /// Picture picture = sheet.picutes.addStream(1, 1, bytes); + /// picture.column = 2; + /// List bytes = workbook.saveAsStream(); + /// File('Picutes.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` int column; /// Gets/Sets the image last row. + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// List bytes = File('image.png').readAsBytesSync(); + /// Picture picture = sheet.picutes.addStream(1, 1, bytes); + /// picture.lastRow = 15; + /// List bytes = workbook.saveAsStream(); + /// File('Picutes.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` int lastRow; /// Gets/Sets the image last column. + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// List bytes = File('image.png').readAsBytesSync(); + /// Picture picture = sheet.picutes.addStream(1, 1, bytes); + /// picture.lastColumn = 15; + /// List bytes = workbook.saveAsStream(); + /// File('Picutes.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` int lastColumn; /// Gets/Sets the image width. + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// List bytes = File('image.png').readAsBytesSync(); + /// Picture picture = sheet.picutes.addStream(1, 1, bytes); + /// picture.width = 150; + /// List bytes = workbook.saveAsStream(); + /// File('Picutes.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` int width; /// Gets/Sets the image height. + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// List bytes = File('image.png').readAsBytesSync(); + /// Picture picture = sheet.picutes.addStream(1, 1, bytes); + /// picture.height = 100; + /// List bytes = workbook.saveAsStream(); + /// File('Picutes.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` int height; /// Gets/Sets the image horizontal flip. + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// List bytes = File('image.png').readAsBytesSync(); + /// Picture picture = sheet.picutes.addStream(1, 1, bytes); + /// picture.horizontalFlip = true; + /// List bytes = workbook.saveAsStream(); + /// File('Picutes.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` bool horizontalFlip = false; /// Gets/Sets the image vertical flip. + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// List bytes = File('image.png').readAsBytesSync(); + /// Picture picture = sheet.picutes.addStream(1, 1, bytes); + /// picture.verticalFlip = true; + /// List bytes = workbook.saveAsStream(); + /// File('Picutes.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` bool verticalFlip = false; /// Gets/Sets the image rotation. + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// List bytes = File('image.png').readAsBytesSync(); + /// Picture picture = sheet.picutes.addStream(1, 1, bytes); + /// picture.rotation = 30; + /// List bytes = workbook.saveAsStream(); + /// File('Picutes.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` int rotation = 0; /// Gets/Sets the image last row offset. @@ -57,6 +156,10 @@ class Picture { return _imageData; } + // ignore: prefer_final_fields + bool _isHyperlink = false; + Hyperlink _link; + /// Check whether the picture is png. static bool isPng(List imageData) { if (imageData.length >= _pngSignature.length) { diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/images/pictures_collection.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/images/pictures_collection.dart index 0b3a9ab8b..dc646e836 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/images/pictures_collection.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/images/pictures_collection.dart @@ -35,19 +35,16 @@ class PicturesCollection { } /// Add styles to the collection - Picture addFile(int topRow, int leftColumn, String fileName) { - if (fileName == null || fileName == '') { - throw Exception('name should not be null or empty'); - } - - final Picture picture = Picture(File(fileName).readAsBytesSync()); - picture.row = topRow; - picture.column = leftColumn; - _pictures.add(picture); - return picture; - } - - /// Add styles to the collection + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// List bytes = File('image.png').readAsBytesSync(); + /// sheet.picutes.addStream(1, 1, bytes); + /// List bytes = workbook.saveAsStream(); + /// File('Picutes.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` Picture addStream(int topRow, int leftColumn, List stream) { if (stream == null || stream.isEmpty) { throw Exception('stream should not be null or empty'); @@ -61,6 +58,16 @@ class PicturesCollection { } /// Add styles to the collection + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// String base64Image = base64Encode(File('image.png').readAsBytesSync()); + /// sheet.picutes.addBase64(1, 1, base64Image); + /// List bytes = workbook.saveAsStream(); + /// File('Picutes.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` Picture addBase64(int topRow, int leftColumn, String base64Data) { if (base64Data == null || base64Data == '') { throw Exception('base64Data should not be null or empty'); diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/merged_cells/merged_cell_collection.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/merged_cells/merged_cell_collection.dart index 751940a29..e6c53c44f 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/merged_cells/merged_cell_collection.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/merged_cells/merged_cell_collection.dart @@ -24,12 +24,12 @@ class MergedCellCollection { for (final MergeCell mCell in innerList) { if (MergedCellCollection._isIntersecting(mCell, mergeCell)) { final MergeCell intersectingCell = MergeCell(); - intersectingCell.x = math.min(mCell.x, mergeCell.x); - intersectingCell.y = math.min(mCell.y, mergeCell.y); + intersectingCell.x = min(mCell.x, mergeCell.x); + intersectingCell.y = min(mCell.y, mergeCell.y); intersectingCell.width = - math.max(mCell.width + mCell.y, mergeCell.width + mergeCell.x); + max(mCell.width + mCell.y, mergeCell.width + mergeCell.x); intersectingCell.height = - math.max(mCell.height + mCell.y, mergeCell.height + mergeCell.y); + max(mCell.height + mCell.y, mergeCell.height + mergeCell.y); intersectingCell._reference = (this[count]._reference.split(':')[0].toString()) + ':' + diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/range.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/range.dart index 78ff1ed6c..0f5df24b1 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/range.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/range.dart @@ -65,6 +65,15 @@ class Range { ///Equivalent symbol for formula. static const String _defaultEquivalent = '='; + /// A boolean variable that indicates if display text of this range is called from AutoFitColumns(). + // ignore: prefer_final_fields + bool _bAutofitText = false; + + /// Checks whether Range is a part of merged Range. + bool get _isMerged { + return rowSpan != 0 || columnSpan != 0; + } + /// Gets the Range reference along with its sheet name in format "'Sheet1'!$A$1". Read-only. /// /// ```dart @@ -72,7 +81,8 @@ class Range { /// Worksheet sheet = workbook.worksheets[0]; /// Range range = sheet.getRangeByName('A1'); /// String address = range.addressGlobal; - /// workbook.save('AddressGlobal.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('AddressGlobal.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` String get addressGlobal { @@ -88,6 +98,16 @@ class Range { } /// Represents the reference name. + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// Range range = sheet.getRangeByName('A1'); + /// String address = range.addressLocal; + /// List bytes = workbook.saveAsStream(); + /// File('AddressLocal.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` String get addressLocal { return _getCellName(row, column); } @@ -106,9 +126,10 @@ class Range { /// ```dart /// Workbook workbook = new Workbook(); /// Worksheet sheet = workbook.worksheets[0]; - /// Range range = sheet.getRangeByName('A1'); - /// range.fromula = '=10+10'; - /// workbook.save('Formula.xlsx'); + /// Range range = sheet.getRangeByName('C1'); + /// range.formula = '=A1+B1'; + /// List bytes = workbook.saveAsStream(); + /// File('Formula.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` set formula(String value) { @@ -120,7 +141,7 @@ class Range { if (isSingleRange) { return _number; } else { - return _getNumber(); + return getNumber(); } } @@ -130,7 +151,8 @@ class Range { /// Worksheet sheet = workbook.worksheets[0]; /// Range range = sheet.getRangeByName('A1'); /// range.number = 44; - /// workbook.save('Number.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Number.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` set number(double value) { @@ -153,7 +175,8 @@ class Range { /// Worksheet sheet = workbook.worksheets[0]; /// Range range = sheet.getRangeByName('A1'); /// range.text = 'Hello World'; - /// workbook.save('Text.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Text.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` set text(String value) { @@ -176,7 +199,8 @@ class Range { /// Worksheet sheet = workbook.worksheets[0]; /// Range range = sheet.getRangeByName('A1'); /// range.dateTime = DateTime(2011, 1, 20, 20, 37, 80); - /// workbook.save('DateTime.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('DateTime.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` set dateTime(DateTime value) { @@ -194,7 +218,8 @@ class Range { /// Worksheet sheet = workbook.worksheets[0]; /// Range range = sheet.getRangeByName('A1'); /// range.value = 44; - /// workbook.save('value.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Value.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` set value(Object value) { @@ -210,7 +235,8 @@ class Range { /// range.value = '1/1/2015'; /// range.numberFormat = 'dd-MMM-yyyy'; /// String displayText = range.displayText; - /// workbook.save('displayText.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('DisplayText.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` String get displayText { @@ -228,7 +254,8 @@ class Range { /// //Initializes Calculate Engine to perform calculation /// sheet.enableSheetCalculations(); /// String calculatedValue = range.calculatedValue; - /// workbook.save('CalculatedValue.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('CalculatedValue.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` String get calculatedValue { @@ -289,7 +316,8 @@ class Range { /// range.value = '1/1/2015'; /// range.numberFormat = 'dd-MMM-yyyy'; /// String displayText = range.displayText; - /// workbook.save('NumberFormat.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('NumberFormat.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` set numberFormat(String value) { @@ -336,7 +364,8 @@ class Range { /// Worksheet sheet = workbook.worksheets[0]; /// Style rangeStyle = sheet.getRangeByName('A1').cellStyle; /// rangeStyle.bold = true; - /// workbook.save('CellstyleRange.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('CellStyleRange.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` Style get cellStyle { @@ -364,7 +393,8 @@ class Range { /// workbook.styles.addStyle(cellStyle); /// Range range1 = sheet.getRangeByIndex(1, 1); /// range1.cellStyle = cellStyle; - /// workbook.save('CellstyleRange.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('CellStyleRange.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` set cellStyle(CellStyle value) { @@ -408,7 +438,8 @@ class Range { /// Worksheet sheet = workbook.worksheets[0]; /// Range range = sheet.getRangeByName('A1'); /// range.builtInStyle = BuiltInStyles.accent1; - /// workbook.save('BuiltInStyle.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('BuiltInStyle.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` set builtInStyle(BuiltInStyles value) { @@ -444,14 +475,15 @@ class Range { return 0; } - /// sets the height of all the rows in the range, measured in points. + /// Sets the height of all the rows in the range, measured in points. /// /// ```dart /// Workbook workbook = new Workbook(); /// Worksheet sheet = workbook.worksheets[0]; /// Range range = sheet.getRangeByName('A1'); /// range.rowHeight = 25; - /// workbook.save('RowHeight.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('RowHeight.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` set rowHeight(double value) { @@ -491,7 +523,8 @@ class Range { /// Worksheet sheet = workbook.worksheets[0]; /// Range range = sheet.getRangeByName('A1'); /// range.columnWidth = 25; - /// workbook.save('ColumnWidth.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('ColumnWidth.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` set columnWidth(double value) { @@ -526,9 +559,10 @@ class Range { /// List cells = sheet.getRangeByName('A1').cells; /// for (Range range in cells) /// { - /// // Do some manipulations - /// } - /// workbook.save('Cells.xlsx'); + /// // Do some manipulations + /// } + /// List bytes = workbook.saveAsStream(); + /// File('RangeCells.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` List get cells { @@ -542,12 +576,13 @@ class Range { final RowCollection rows = worksheet.rows; if (rows[row] != null) { currRow = rows[row]; + currRow.ranges[column] = this; } else { currRow = Row(worksheet); currRow.index = row; rows[row] = currRow; + currRow.ranges[column] = this; } - currRow.ranges[column] = this; } /// Set number value to the range. @@ -557,7 +592,8 @@ class Range { /// Worksheet sheet = workbook.worksheets[0]; /// Range range = sheet.getRangeByName('A1'); /// range.setNumber(44); - /// workbook.save('Number.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Number.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` void setNumber(double number) { @@ -580,7 +616,7 @@ class Range { } /// Set number value to the range. - double _getNumber() { + double getNumber() { final double dValue = worksheet.getRangeByIndex(row, column).number; if (dValue == null) return dValue; // ignore: prefer_final_locals @@ -601,7 +637,8 @@ class Range { /// Worksheet sheet = workbook.worksheets[0]; /// Range range = sheet.getRangeByName('A1'); /// range.setText('Hello'); - /// workbook.save('Text.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Text.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` void setText(String text) { @@ -669,7 +706,8 @@ class Range { /// Worksheet sheet = workbook.worksheets[0]; /// Range range = sheet.getRangeByName('A1'); /// range.setDateTime(DateTime(2011, 1, 20, 20, 37, 80)); - /// workbook.save('DateTime.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('DateTime.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` void setDateTime(DateTime dateTime) { @@ -728,6 +766,16 @@ class Range { } /// Set formula value to the range. + /// + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// Range range = sheet.getRangeByName('C1'); + /// range.setFormula('=A1+B1'); + /// List bytes = workbook.saveAsStream(); + /// File('Formula.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` void setFormula(String formula) { if (isSingleRange) { if (formula[0] != '=') formula = '=' + formula; @@ -770,7 +818,8 @@ class Range { /// Worksheet sheet = workbook.worksheets[0]; /// Range range = sheet.getRangeByName('A1'); /// range.setValue('Hello World'); - /// workbook.save('value.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Value.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` void setValue(Object value) { @@ -959,7 +1008,7 @@ class Range { displayText = dValue.toString(); } else if (dValue.isInfinite) { return '#DIV/0!'; - } else { + } else if (numberFormat != 'General' && numberFormat != '@') { final NumberFormat formatter = NumberFormat(numberFormat, workbook._culture); String displayText = formatter.format(dValue); @@ -972,6 +1021,12 @@ class Range { displayText = displayText.replaceAll('-??', ''); } return displayText; + } else { + String displayText = dValue.toString(); + if (displayText.endsWith('.0')) { + displayText = displayText.substring(0, displayText.length - 2); + } + return displayText; } } } @@ -1192,7 +1247,8 @@ class Range { /// Range range1 = sheet.getRangeByName('A1:D4'); /// //Merging cells /// range1.merge(); - /// workbook.save('Merge.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Merge.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` void merge() { @@ -1223,7 +1279,8 @@ class Range { /// Range range1 = sheet.getRangeByName('A1:D4'); /// //Merging cells /// range1.unmerge(); - /// workbook.save('UnMerge.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Unmerge.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` void unmerge() { @@ -1254,6 +1311,111 @@ class Range { } } + /// Changes the width of the columns and height of the rows in the Range to achieve the best fit. + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// + /// //Auto-fit columns + /// Range range = sheet.getRangeByName('A1:D4'); + /// range.autofit(); + /// + /// List bytes = workbook.saveAsStream(); + /// File('AutoFit.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + void autoFit() { + autoFitColumns(); + autoFitRows(); + } + + /// Changes the width of the columns in the Range to achieve the best fit. + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// + /// //Auto-fit columns + /// Range range = sheet.getRangeByName('A1:D4'); + /// range.autofitColumns(); + /// + /// List bytes = workbook.saveAsStream(); + /// File('AutoFitColumns.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + void autoFitColumns() { + _autoFitToColumn(row, lastRow); + } + + /// Changes the height of the rows in the Range to achieve the best fit. + /// ```dart + /// Workbook workbook = new Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// + /// //Auto-fit columns + /// Range range = sheet.getRangeByName('A1:D4'); + /// range.autofitRows(); + /// + /// List bytes = workbook.saveAsStream(); + /// File('AutoFitRows.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + void autoFitRows() { + for (int i = row; i <= lastRow; i++) { + _worksheet._autoFitToRow(i, column, lastColumn); + } + } + + /// Auto fits the column from specified first to last column. + void _autoFitToColumn(int firstColumn, int lastColumn) { + final int iFirstRow = row; + final int iLastRow = lastRow; + if (iFirstRow == 0 || iLastRow == 0 || iFirstRow > iLastRow) { + return; + } + + if (firstColumn < 1 || firstColumn > workbook._maxColumnCount) { + throw Exception('firstColumn'); + } + + if (lastColumn < 1 || lastColumn > workbook._maxColumnCount) { + throw Exception('lastColumn'); + } + + final _AutoFitManager autoFitManager = + _AutoFitManager(iFirstRow, firstColumn, iLastRow, lastColumn, this); + autoFitManager._measureToFitColumn(); + } + + /// Determines whether the Range is Merged or not. + static List _isMergedCell(Range range, bool isRow, int num4) { + if (range._rowSpan != 0 && range._colSpan != 0) { + if (isRow && range._rowSpan == 1) { + num4 = range._colSpan; + } else if (range._colSpan == 1) { + num4 = range._rowSpan; + } + return [num4, true]; + } + return [num4, false]; + } + + /// Sets row height. + void _setRowHeight(double value, bool bIsBadFontHeight) { + if (value < 0 || value > _worksheet._defaultMaxHeight) { + throw Exception('Row Height must be in range from 0 to 409.5'); + } + int firstRowValue = row; + int lastRowValue = lastRow; + if (((lastRow - row) > (workbook._maxRowCount - (lastRow - row))) && + lastRow == workbook._maxRowCount) { + firstRowValue = 1; + lastRowValue = row - 1; + } + for (int i = firstRowValue; i <= lastRowValue; i++) { + _worksheet._innerSetRowHeight(i, value, bIsBadFontHeight, 6); + } + } + /// Releases the unmanaged resources used by the XmlReader and optionally releases the managed resources. void _clear() { if (_cellStyle != null) { diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/range_collection.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/range_collection.dart index eed2402a2..c4c31aba3 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/range_collection.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/range_collection.dart @@ -66,6 +66,18 @@ class RangeCollection { return range; } + /// Get a cell from cells collection based on column. + Range _getCell(int columnIndex) { + for (final Range range in innerList) { + if (range != null) { + if (range._index == columnIndex) { + return range; + } + } + } + return null; + } + /// clear the Range. void _clear() { if (_innerList != null) { diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/row_collection.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/row_collection.dart index 68f103587..3801f38d8 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/row_collection.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/row_collection.dart @@ -71,6 +71,18 @@ class RowCollection { return row; } + /// Get a row from rows collection based on row index. + Row _getRow(int rowIndex) { + for (final Row row in innerList) { + if (row != null) { + if (row.index == rowIndex) { + return row; + } + } + } + return null; + } + /// Clear the row. void _clear() { if (_innerList != null) { diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/security/excel_sheet_protection.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/security/excel_sheet_protection.dart new file mode 100644 index 000000000..516b8dd6a --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/security/excel_sheet_protection.dart @@ -0,0 +1,59 @@ +part of xlsio; + +/// Represents worksheet protection options. +class ExcelSheetProtectionOption { + /// Create instances of class for sheet protection; + static ExcelSheetProtectionOption excelSheetProtectionOption = + ExcelSheetProtectionOption(); + + /// Represents whether the content should be protected. + bool content = false; + + /// Represents whether the shapes should be protected. + bool objects = false; + + /// Represents whether the scenarios should be protected. + bool scenarios = false; + + /// Allows the user to format any cell on a protected worksheet when this is set as true. + bool formatCells = false; + + /// Allows the user to format any column on a protected worksheet when this is set as true. + bool formatColumns = false; + + /// Allows the user to format any row on a protected when this is set as true. + bool formatRows = false; + + /// Allows the user to insert columns on the protected worksheet when this is set as true. + bool insertColumns = false; + + /// Allows the user to insert rows on the protected worksheet when this is set as true. + bool insertRows = false; + + /// Allows the user to insert hyperlinks on the worksheet when this is set as true. + bool insertHyperlinks = false; + + /// Allows the user to delete columns on the protected worksheet, where every cell in the column to be deleted is unlocked when this is set as true. + bool deleteColumns = false; + + /// Allows the user to delete rows on the protected worksheet, where every cell in the row to be deleted is unlocked when this is set as true. + bool deleteRows = false; + + /// Represents whether the locked cells should be protected. + bool lockedCells = false; + + /// Allows the user to sort on the protected worksheet when this is set as true. + bool sort = false; + + /// Allows the user to set filters on the protected worksheet when this is set as true. Users can change filter criteria but cannot enable or disable an auto filter. + bool useAutoFilter = false; + + /// Allows the user to use pivot table reports on the protected worksheet when this is set as true. + bool usePivotTableAndPivotChart = false; + + /// Represents whether user interface should be protected but not macros. + bool unlockedCells = false; + + /// Allows the user to use all the Excel sheet protection options when this is set as true. + bool all = false; +} diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/security/security_helper.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/security/security_helper.dart new file mode 100644 index 000000000..dc607a44a --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/security/security_helper.dart @@ -0,0 +1,38 @@ +part of xlsio; + +/// This class contains utility methods used by Excel 2007 security implementation. +class _SecurityHelper { + /// Represents the SH512 Hash Algorithm. + static const String _sha512Alogrithm = 'SHA-512'; + + /// Gets HashAlgorithm from the Algorithm name + // ignore: unused_element + static Hash _getAlgorithm(String algorithmName) { + switch (algorithmName) { + case 'SHA-512': + return sha512; + case 'SHA-1': + return sha1; + case 'SHA-256': + return sha256; + default: + return sha1; + } + } + + /// Combines two arrays into one. + // ignore: unused_element + static List _combineArray(List buffer1, List buffer2) { + final int iLength1 = buffer1.length; + final int iLength2 = buffer2.length; + final int iCombinedLength = iLength1 + iLength2; + final List arrResult = List(iCombinedLength); + for (int i = 0; i < iLength1; i++) { + arrResult[i] = buffer1[i]; + } + for (int j = iLength1, z = 0; j < arrResult.length; j++, z++) { + arrResult[j] = buffer2[z]; + } + return arrResult; + } +} diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/worksheet/worksheet.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/worksheet/worksheet.dart index 810f378dd..3e1c1deda 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/worksheet/worksheet.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/worksheet/worksheet.dart @@ -16,17 +16,45 @@ class Worksheet { /// Represents Worksheet's name. String _name; + /// Standard column width. + final double _standardWidth = 8.43; + + /// Standard column width. + final double _standardHeight = 12.5; + + /// Default character (for width measuring). + final String _defaultStandardChar = '0'; + + /// Maximum row height in points. + final double _defaultMaxHeight = 409.5; + + /// One degree in radians. + final double _defaultAxeInRadians = pi / 180; + + /// Represents indent width. + final int _defaultIndentWidth = 12; + + /// Represent the hyperlink relation id + final List _hyperlinkRelationId = []; + + /// Represents auto fit manager. + _AutoFitManager get _autoFitManager { + final autoFit = _AutoFitManager._withSheet(this); + return autoFit; + } + /// Sets the grid line visible. /// True if grid lines are visible. otherwise, False. /// /// ```dart /// Workbook workbook = new Workbook(); /// Worksheet sheet = workbook.worksheets[0]; - /// sheet.showGridLines = false; - /// workbook.save('GridLine.xlsx'); + /// sheet.showGridlines = false; + /// List bytes = workbook.saveAsStream(); + /// File('Gridline.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` - bool showGridLines = true; + bool showGridlines = true; /// Collection of all pictures in the current worksheet. PicturesCollection _pictures; @@ -45,7 +73,9 @@ class Worksheet { /// ChartCollection charts = ChartCollection(sheet); /// charts.add(); /// sheet.charts = charts; - /// workbook.save('ExcelEmptyChart.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('ExcelEmptyChart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` ChartHelper charts; @@ -61,18 +91,40 @@ class Worksheet { ///Collection of all merged cells in the current worksheet. MergedCellCollection _mergeCells; + /// Collection of all hyperlinks in the current worksheet. + HyperlinkCollection _hyperlinks; + /// Represents parent workbook. Workbook get workbook { return _book; } /// Represents all the columns in the specified worksheet. + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// Column col = sheet.columns.add(); + /// col.width = 25; + /// List bytes = workbook.saveAsStream(); + /// File('ExcelColumns.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` ColumnCollection get columns { _columns ??= ColumnCollection(this); return _columns; } /// Returns or sets the name of the worksheet. + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// sheet.name = 'MySheet'; + /// List bytes = workbook.saveAsStream(); + /// File('ExcelSheetName.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` String get name { if (_name == null || _name == '') { _name = 'Sheet' + (index).toString(); @@ -94,14 +146,29 @@ class Worksheet { _mergeCells = value; } - /// Gets/Sets a pictures collections in the worksheet. /// Gets/Sets a pictures collections in the worksheet. PicturesCollection get pictures { _pictures ??= PicturesCollection(this); return _pictures; } + /// Gets/Sets a hyperlink collections in the worksheet. + HyperlinkCollection get hyperlinks { + _hyperlinks ??= HyperlinkCollection(this); + return _hyperlinks; + } + /// Gets/Sets a rows collections in the worksheet. + /// + /// ```dart + /// Workbook workbook = Workbook(); + /// Worksheet sheet = workbook.worksheets[0]; + /// Row row = sheet.rows.add(); + /// row.height = 25; + /// List bytes = workbook.saveAsStream(); + /// File('ExcelRows.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` RowCollection get rows { _rows ??= RowCollection(this); return _rows; @@ -124,7 +191,8 @@ class Worksheet { /// Workbook workbook = new Workbook(); /// Worksheet sheet = workbook.worksheets[0]; /// sheet.getRangeByIndex(1, 1, 2, 2); - /// workbook.save('Range.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Range.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` Range getRangeByIndex(int rowIndex, int columnIndex, @@ -161,7 +229,8 @@ class Worksheet { /// Workbook workbook = Workbook(); /// Worksheet sheet = workbook.worksheets[0]; /// sheet.getRangeByName('A1:D4'); - /// workbook.save('Range.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Range.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` Range getRangeByName(String cellReference) { @@ -342,6 +411,9 @@ class Worksheet { /// Range range1 = sheet.getRangeByIndex(1, 2); /// range1.formula = '=A1'; /// sheet.enableSheetCalculations(); + /// List bytes = workbook.saveAsStream(); + /// File('Formulas.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` void enableSheetCalculations() { _book._enabledCalcEngine = true; @@ -354,7 +426,6 @@ class Worksheet { calcEngine = CalcEngine(this); calcEngine.useDatesInCalculations = true; calcEngine.useNoAmpersandQuotes = true; - calcEngine.excelLikeComputations = true; final int sheetFamilyID = CalcEngine.createSheetFamilyID(); @@ -432,8 +503,10 @@ class Worksheet { isNumber = _checkIsNumber(value, cultureInfo); } - for (final String v in calcEngine._formulaErrorStrings) { - if (value == v) istext = true; + if (value != null && + value.isNotEmpty && + calcEngine._formulaErrorStrings.contains(value)) { + istext = true; } if (valType == CellType.formula) { @@ -575,7 +648,7 @@ class Worksheet { final int firstRow = getFirstRow(); if (firstRow != -1) { int firstCol = rows[firstRow].index; - for (int i = firstRow + 1; i < rows.count; i++) { + for (int i = firstRow; i < rows.count; i++) { final Row row = rows[i]; if (row != null) { for (final Range cell in row.ranges.innerList) { @@ -590,12 +663,999 @@ class Worksheet { return -1; } + /// Changes the width of the specified column to achieve the best fit. + void autoFitColumn(int colIndex) { + final Range range = getRangeByIndex( + getFirstRow(), getFirstColumn(), getLastRow(), getLastColumn()); + range._autoFitToColumn(colIndex, colIndex); + } + + /// Changes the height of the specified row to achieve the best fit. + void autoFitRow(int rowIndex) { + final int iFirstColumn = getFirstColumn(); + final int iLastColumn = getLastColumn(); + _autoFitToRow(rowIndex, iFirstColumn, iLastColumn); + } + + /// Returns the width of the specified column. + double getColumnWidth(int iColumnIndex) { + if (iColumnIndex < 1 || iColumnIndex > _book._maxColumnCount) { + throw Exception( + 'Value cannot be less 1 and greater than max column index.'); + } + return _innerGetColumnWidth(iColumnIndex); + } + + /// Returns the height of the specified row. + double getRowHeight(int iRow) { + return _innerGetRowHeight(iRow, true); + } + + /// Returns height from RowRecord if there is a corresponding Row. + double _innerGetRowHeight(int iRow, bool bRaiseEvents) { + if (iRow < 1 || iRow > _book._maxRowCount) { + throw Exception('Value cannot be less 1 and greater than max row index.'); + } + Row row = rows[iRow]; + if (rows[iRow] == null) { + row = Row(this); + row.index = iRow; + rows[iRow] = row; + } + + bool hasMaxHeight = false; + bool hasRotation = false; + + if (row != null) { + final int firstColumn = getFirstColumn(); + final lastColumn = getLastColumn(); + final Range rowRange = + getRangeByIndex(iRow, firstColumn, iRow, lastColumn); + + if (firstColumn > 0 && + lastColumn > 0 && + (_standardHeight == row.height && + !(rowRange.cellStyle.rotation > 0)) || + (rowRange.cellStyle.wrapText && + !(rowRange.columnSpan != 0 && + rowRange.columnSpan == lastColumn - firstColumn))) { + return row.height; + } else if (row.height == 0) { + return _standardHeight; + } else if (firstColumn <= _book._maxColumnCount && + lastColumn <= _book._maxColumnCount) { + final double standardFontSize = _book._standardFontSize; + for (final Range migrantCell in row.ranges.innerList) { + if (migrantCell != null) { + final Style style = migrantCell.cellStyle; + final double fontSize = style.fontSize; + final String fontName = style.fontName; + if (migrantCell.rowSpan == 0) { + if (!hasRotation && + style.rotation > 0 && + migrantCell.columnSpan == 0 && + row.height == 0) { + hasMaxHeight = true; + hasRotation = true; + _autoFitToRow(iRow, firstColumn, lastColumn); + break; + } + if (fontSize > standardFontSize || + fontName != _book._standardFont || + style.rotation > 0) { + hasMaxHeight = true; + if (row.height == _standardHeight) { + _autoFitToRow(iRow, firstColumn, lastColumn); + } else if ((fontName != _book._standardFont) && + !(fontSize > standardFontSize || (style.rotation) > 0)) { + if (row.height - _standardHeight > 5) { + _autoFitToRow(iRow, firstColumn, lastColumn); + } + } + break; + } + } + } + } + } + } + + if (hasMaxHeight) { + return row.height; + } else { + return _standardHeight; + } + } + + /// Autofits row by checking only the cells in the row that are specified by column range. + void _autoFitToRow(int rowIndex, int firstColumn, int lastColumn) { + if (firstColumn == 0 || lastColumn == 0 || firstColumn > lastColumn) { + return; + } + + final _SizeF maxSize = _SizeF(0, 0); + _SizeF curSize; + bool hasRotation = false; + bool isMergedAndWrapped = false; + for (int j = firstColumn; j <= lastColumn; j++) { + if (rows[rowIndex] == null || rows[rowIndex]._ranges[j] == null) { + continue; + } + final Range range = getRangeByIndex(rowIndex, j); + if (range._rowSpan > 1) { + continue; + } + final List result = _measureCell(range, true, false, isMergedAndWrapped); + curSize = result[0]; + isMergedAndWrapped = result[1]; + if (maxSize._height < curSize._height && + !(range.number != null && + _book._standardFontSize == range.cellStyle.fontSize)) { + maxSize._height = curSize._height; + } + if (range.cellStyle.rotation > 0 && + maxSize._height < curSize._width && + !range._isMerged && + !range.cellStyle.wrapText) { + maxSize._height = curSize._width; + hasRotation = true; + } + } + + if (maxSize._height == 0) { + maxSize._height = + _book._measureString(_defaultStandardChar, _book.fonts[0])._height; + } + + double newHeight; + if (!hasRotation) { + newHeight = _book._convertFromPixel(maxSize._height, 6); + } else { + newHeight = _book._convertFromPixel(maxSize._height + _standardWidth, 6); + } + + if (newHeight > _defaultMaxHeight) { + newHeight = _defaultMaxHeight; + } + + final Range range = getRangeByIndex(rowIndex, firstColumn); + if (newHeight > _standardHeight) { + range._setRowHeight(newHeight, isMergedAndWrapped); + } else { + range._setRowHeight(_standardHeight, isMergedAndWrapped); + } + } + + /// Sets inner row height. + void _innerSetRowHeight( + int iRowIndex, double value, bool bIsBadFontHeight, int units) { + value = _book._convertUnits(value, units, 6); + + Row rowObj = rows[iRowIndex]; + if (rows[iRowIndex] == null) { + rowObj = Row(this); + rowObj.index = iRowIndex; + rows[iRowIndex] = rowObj; + } + if (rowObj.height != value) { + rowObj.height = value; + } + } + + /// Gets size of string that contain cell found by cellindex. + List _measureCell(Range range, bool bAutoFitRows, bool ignoreRotation, + bool bIsMergedAndWrapped) { + final int iColumn = range.column; + bool isMerged = false; + final String strText = range.text; + + if (strText == null || strText.isEmpty) { + bIsMergedAndWrapped = false; + return [_SizeF(0, 0), bIsMergedAndWrapped]; + } + + if (range.rowSpan != 0 || range.columnSpan != 0) { + isMerged = true; + } + + final CellStyle format = range.cellStyle; + final Font font = Font(); + font.name = format.fontName; + font.size = format.fontSize; + final int rotation = format.rotation; + _SizeF curSize = _book._measureStringSpecial(strText, font); + + if (bAutoFitRows) { + final double indentLevel = format.indent.toDouble(); + double colWidth = _getColumnWidthInPixels(iColumn).toDouble(); + double defWidth = 0; + if (indentLevel > 0 || rotation == 255) { + final Font fontStyle = + Font._withNameSize(format.fontName, format.fontSize); + final Rectangle rectF = Rectangle(0, 0, 1800, 100); + defWidth = + ((_book._getMeasuredRectangle('0', fontStyle, rectF).width + 0.05)); + + if (rotation == 255) { + defWidth += _standardWidth; + } + final double indentWidth = indentLevel * defWidth; + if (indentWidth < colWidth) { + colWidth -= indentWidth; + } else { + colWidth = defWidth; + } + } + if ((!isMerged) && + format.wrapText && + !((range.number == null && + range.text == null && + range.formula == null) && + workbook._standardFontSize != format.fontSize)) { + final double value = _autoFitManager._calculateWrappedCell( + format, strText, colWidth.toInt()); + if (range.number != null) { + curSize._width = value; + } else { + curSize._height = value; + } + } + if (format.wrapText && rotation > 0) { + ignoreRotation = true; + } + + if (!ignoreRotation && !isMerged && rotation > 0) { + if ((rotation == 90 || rotation == 180)) { + if (range != null) {} + } else if (rotation == 255) { + curSize._width = (_book._convertToPixels( + _autoFitManager + ._calculateWrappedCell(format, strText, defWidth.toInt()) + .toDouble(), + 6) - + defWidth); + } else { + curSize._width = + _updateTextWidthOrHeightByRotation(curSize, rotation, false); + } + } + } else { + curSize = _updateAutofitByIndent(curSize, format); + + if (!ignoreRotation) { + curSize._width = + _updateTextWidthOrHeightByRotation(curSize, rotation, false); + } + } + bIsMergedAndWrapped = isMerged && format.wrapText; + + return [curSize, bIsMergedAndWrapped]; + } + + /// Updates indent size. + _SizeF _updateAutofitByIndent(_SizeF curSize, Style format) { + if (format == null) throw Exception('format'); + + final bool bFlag = + format.hAlign != HAlignType.left && format.hAlign != HAlignType.right; + + if (bFlag && format.rotation != 0 && format.indent == 0) { + return curSize; + } + + curSize._width += format.indent * _defaultIndentWidth; + + return curSize; + } + + /// Updates text width by rotation. + double _updateTextWidthOrHeightByRotation( + _SizeF size, int rotation, bool bUpdateHeight) { + if (rotation == 0) { + return bUpdateHeight ? size._height : size._width; + } + + if (rotation == 90 || rotation == 180) { + return bUpdateHeight ? size._width : size._height; + } + + if (rotation > 90) rotation -= 90; + + if (bUpdateHeight) rotation = 90 - rotation; + + final double fPart = sin(_defaultAxeInRadians * rotation) * size._height; + final double fResult = cos(_defaultAxeInRadians * rotation) * size._width; + + return fResult + fPart; + } + /// This API supports the .NET Framework infrastructure and is not intended to be used directly from your code. // ignore: unused_element int _getColumnCount() { return getLastColumn() - getFirstColumn(); } + /// Inserts an empty row in the specified row index. + void insertRow(int rowIndex, + [int rowCount, ExcelInsertOptions insertOptions]) { + if (rowIndex < 1 || rowIndex > workbook._maxRowCount) { + throw Exception('rowIndex'); + } + rowCount ??= 1; + if (rowCount < 0) throw Exception('count'); + insertOptions ??= ExcelInsertOptions.formatDefault; + final bool isLastRow = (rowIndex + rowCount) >= workbook._maxRowCount; + final int columnIndex = 1; + final int lastRow = getLastRow(); + if (!isLastRow) { + for (int count = 1; count <= rowCount; count++) { + for (int i = lastRow + rowCount; i >= rowIndex; i--) { + final Row row = rows[i]; + if (row == null && i != rowIndex && rows[i - 1] != null) { + rows[i] = Row(this); + rows[i] = rows[i - 1]; + rows[i].index = rows[i].index + 1; + for (int j = rows[i].ranges.innerList.length - 1; j >= 1; j--) { + final Range range = rows[i].ranges[j]; + if (range != null) { + rows[i].ranges[j].row = rows[i].ranges[j].row + 1; + rows[i].ranges[j].lastRow = rows[i].ranges[j].lastRow + 1; + rows[i].ranges[j].column = rows[i].ranges[j].column; + rows[i].ranges[j].lastColumn = rows[i].ranges[j].lastColumn; + } + } + } else if (row != null && i != rowIndex && rows[i - 1] != null) { + rows[i] = rows[i - 1]; + rows[i].index = rows[i].index + 1; + for (int j = rows[i].ranges.innerList.length - 1; j >= 1; j--) { + final Range range = rows[i].ranges[j]; + if (range != null) { + rows[i].ranges[j].row = rows[i].ranges[j].row + 1; + rows[i].ranges[j].lastRow = rows[i].ranges[j].lastRow + 1; + rows[i].ranges[j].column = rows[i].ranges[j].column; + rows[i].ranges[j].lastColumn = rows[i].ranges[j].lastColumn; + } + } + } else if (i == rowIndex) { + rows[i] = Row(this); + rows[i].index = rowIndex; + if (insertOptions == ExcelInsertOptions.formatAsBefore) { + if (rows[i - 1] != null) { + if (rows[i - 1].height != null) { + rows[i].height = rows[i - 1].height; + } + for (int z = 1; + z <= rows[i - 1].ranges.innerList.length - 1; + z++) { + if (rows[i - 1].ranges[z] != null) { + rows[i].ranges[z] = Range(this); + rows[i].ranges[z]._index = rows[i - 1].ranges[z]._index; + rows[i].ranges[z].row = rows[i - 1].ranges[z].row + 1; + rows[i].ranges[z].lastRow = + rows[i - 1].ranges[z].lastRow + 1; + rows[i].ranges[z].column = rows[i - 1].ranges[z].column; + rows[i].ranges[z].lastColumn = + rows[i - 1].ranges[z].lastColumn; + rows[i].ranges[z].cellStyle = + rows[i - 1].ranges[z].cellStyle; + } + } + } + } else if (insertOptions == ExcelInsertOptions.formatAsAfter) { + if (rows[i + 1] != null) { + if (rows[i + 1].height != null) { + rows[i].height = rows[i + 1].height; + } + for (int z = 1; + z <= rows[i + 1].ranges.innerList.length - 1; + z++) { + if (rows[i + 1].ranges[z] != null) { + rows[i].ranges[z] = Range(this); + rows[i].ranges[z]._index = rows[i + 1].ranges[z]._index; + rows[i].ranges[z].row = rows[i + 1].ranges[z].row - 1; + rows[i].ranges[z].lastRow = + rows[i + 1].ranges[z].lastRow - 1; + rows[i].ranges[z].column = rows[i + 1].ranges[z].column; + rows[i].ranges[z].lastColumn = + rows[i + 1].ranges[z].lastColumn; + rows[i].ranges[z].cellStyle = + rows[i + 1].ranges[z].cellStyle; + } + } + } + } else { + rows[i].ranges[columnIndex] = null; + } + if (hyperlinks.count > 0) { + for (final Hyperlink link in hyperlinks.innerList) { + if (link._attachedType == ExcelHyperlinkAttachedType.range && + link._row >= rowIndex) { + link._row = link._row + 1; + } + } + } + } else { + rows[i] = Row(this); + rows[i].index = i; + } + } + } + } + } + + /// Delete the Row in the Worksheet. + void deleteRow(int rowIndex, [int rowCount]) { + if (rowIndex < 1 || rowIndex > workbook._maxRowCount) { + throw Exception('rowIndex'); + } + rowCount ??= 1; + if (rowCount < 0) throw Exception('count'); + for (int count = 1; count <= rowCount; count++) { + final int lastRow = getLastRow(); + for (int i = rowIndex; i <= lastRow; i++) { + final Row row = rows[i]; + if (row != null && i != lastRow && rows[i + 1] != null) { + rows[i] = rows[i + 1]; + rows[i].index = rows[i].index - 1; + for (int j = rows[i].ranges.innerList.length - 1; j >= 1; j--) { + final Range range = rows[i].ranges[j]; + if (range != null) { + rows[i].ranges[j].row = rows[i].ranges[j].row - 1; + rows[i].ranges[j].lastRow = rows[i].ranges[j].lastRow - 1; + rows[i].ranges[j].column = rows[i].ranges[j].column; + rows[i].ranges[j].lastColumn = rows[i].ranges[j].lastColumn; + } + } + } else if (row == null && i != lastRow && rows[i + 1] != null) { + rows[i] = Row(this); + rows[i] = rows[i + 1]; + rows[i].index = rows[i].index - 1; + for (int j = rows[i].ranges.innerList.length - 1; j >= 1; j--) { + final Range range = rows[i].ranges[j]; + if (range != null) { + rows[i].ranges[j].row = rows[i].ranges[j].row - 1; + rows[i].ranges[j].lastRow = rows[i].ranges[j].lastRow - 1; + rows[i].ranges[j].column = rows[i].ranges[j].column; + rows[i].ranges[j].lastColumn = rows[i].ranges[j].lastColumn; + } + } + } else if (i == lastRow) { + rows[i] = null; + if (hyperlinks.count > 0) { + for (int z = 0; z < hyperlinks.count; z++) { + if (hyperlinks[z]._attachedType == + ExcelHyperlinkAttachedType.range && + hyperlinks[z]._row > rowIndex) { + hyperlinks[z]._row = hyperlinks[z]._row - 1; + } + } + } + } else { + rows[i] = rows[i + 1]; + } + } + } + } + + /// Inserts an empty column for the specified column index. + void insertColumn(int columnIndex, + [int columnCount, ExcelInsertOptions insertOptions]) { + if (columnIndex < 1 || columnIndex > workbook._maxColumnCount) { + throw Exception( + 'Value cannot be less 1 and greater than max column index.'); + } + columnCount ??= 1; + if (columnCount < 0) throw Exception('count'); + insertOptions ??= ExcelInsertOptions.formatDefault; + final int firstRow = getFirstRow(); + final int lastRow = getLastRow(); + final int lastColumn = getLastColumn(); + for (int i = lastRow; i >= firstRow; i--) { + if (rows[i] != null) { + for (int count = 1; count <= columnCount; count++) { + for (int j = lastColumn + columnCount; j >= columnIndex; j--) { + final Range range = rows[i].ranges[j]; + if (range == null && + j != columnIndex && + rows[i].ranges[j - 1] != null) { + final double columnWidth = rows[i].ranges[j - 1].columnWidth; + rows[i].ranges[j] = Range(this); + rows[i].ranges[j] = rows[i].ranges[j - 1]; + rows[i].ranges[j]._index = j; + rows[i].ranges[j].row = rows[i].ranges[j].row; + rows[i].ranges[j].lastRow = rows[i].ranges[j].lastRow; + rows[i].ranges[j].column = rows[i].ranges[j].column + 1; + rows[i].ranges[j].lastColumn = rows[i].ranges[j].lastColumn + 1; + if (rows[i].ranges[j].columnWidth != 0.0) { + rows[i].ranges[j]._columnObj = null; + rows[i].ranges[j].columnWidth = columnWidth; + } + } else if (range != null && + j != columnIndex && + rows[i].ranges[j - 1] != null) { + final double columnWidth = rows[i].ranges[j - 1].columnWidth; + rows[i].ranges[j] = rows[i].ranges[j - 1]; + rows[i].ranges[j]._index = j; + rows[i].ranges[j].row = rows[i].ranges[j].row; + rows[i].ranges[j].lastRow = rows[i].ranges[j].lastRow; + rows[i].ranges[j].column = rows[i].ranges[j].column + 1; + rows[i].ranges[j].lastColumn = rows[i].ranges[j].lastColumn + 1; + if (rows[i].ranges[j].columnWidth != 0.0) { + rows[i].ranges[j]._columnObj = null; + rows[i].ranges[j].columnWidth = columnWidth; + } + } else if (j == columnIndex && + rows[i].ranges[j] == rows[i].ranges[columnIndex]) { + if (insertOptions == ExcelInsertOptions.formatAsBefore) { + if (rows[i].ranges[j - 1] != null) { + rows[i].ranges[j] = Range(this); + rows[i].ranges[j]._index = j; + rows[i].ranges[j].row = rows[i].ranges[j - 1].row; + rows[i].ranges[j].lastRow = rows[i].ranges[j - 1].lastRow; + rows[i].ranges[j].column = rows[i].ranges[j - 1].column + 1; + rows[i].ranges[j].lastColumn = + rows[i].ranges[j - 1].lastColumn + 1; + rows[i].ranges[j].cellStyle = rows[i].ranges[j - 1].cellStyle; + if (rows[i].ranges[j - 1].columnWidth != 0.0) { + rows[i].ranges[j].columnWidth = + rows[i].ranges[j - 1].columnWidth; + } + } else { + rows[i].ranges[j] = null; + } + } else if (insertOptions == ExcelInsertOptions.formatAsAfter) { + if (rows[i].ranges[j + 1] != null) { + rows[i].ranges[j] = Range(this); + rows[i].ranges[j]._index = j; + rows[i].ranges[j].row = rows[i].ranges[j + 1].row; + rows[i].ranges[j].lastRow = rows[i].ranges[j + 1].lastRow; + rows[i].ranges[j].column = rows[i].ranges[j + 1].column - 1; + rows[i].ranges[j].lastColumn = + rows[i].ranges[j + 1].lastColumn - 1; + rows[i].ranges[j].cellStyle = rows[i].ranges[j + 1].cellStyle; + if (rows[i].ranges[j + 1].columnWidth != 0.0) { + rows[i].ranges[j].columnWidth = + rows[i].ranges[j + 1].columnWidth; + } + } + } else { + rows[i].ranges[j] = null; + } + } else { + rows[i].ranges[j] = null; + } + } + } + } + } + if (hyperlinks.count > 0) { + for (final Hyperlink link in hyperlinks.innerList) { + if (link._attachedType == ExcelHyperlinkAttachedType.range && + link._column >= columnIndex) { + link._column = link._column + columnCount; + } + } + } + if (insertOptions == ExcelInsertOptions.formatDefault && + lastColumn >= columnIndex) { + if (columns.count > 0 && columns.count >= columnIndex) { + columns.innerList.removeAt(0); + } + } + } + + /// Delete the Column in the Worksheet. + void deleteColumn(int columnIndex, [int columnCount]) { + if (columnIndex < 1 || columnIndex > workbook._maxColumnCount) { + throw Exception( + 'Value cannot be less 1 and greater than max column index.'); + } + columnCount ??= 1; + if (columnCount < 0) throw Exception('count'); + final int firstRow = getFirstRow(); + final int lastRow = getLastRow(); + final int lastColumn = getLastColumn(); + for (int i = firstRow; i <= lastRow; i++) { + if (rows[i] != null) { + for (int count = 1; count <= columnCount; count++) { + for (int j = columnIndex; j <= lastColumn; j++) { + final Range range = rows[i].ranges[j]; + if (range != null && + j != lastColumn && + rows[i].ranges[j + 1] != null) { + rows[i].ranges[j] = rows[i].ranges[j + 1]; + rows[i].ranges[j]._index = rows[i].ranges[j]._index - 1; + rows[i].ranges[j].row = rows[i].ranges[j].row; + rows[i].ranges[j].lastRow = rows[i].ranges[j].lastRow; + rows[i].ranges[j].column = rows[i].ranges[j].column - 1; + rows[i].ranges[j].lastColumn = rows[i].ranges[j].lastColumn - 1; + } else if (range == null && + j != lastColumn && + rows[i].ranges[j + 1] != null) { + rows[i].ranges[j] = Range(this); + rows[i].ranges[j] = rows[i].ranges[j + 1]; + rows[i].ranges[j]._index = rows[i].ranges[j]._index - 1; + rows[i].ranges[j].row = rows[i].ranges[j].row; + rows[i].ranges[j].lastRow = rows[i].ranges[j].lastRow; + rows[i].ranges[j].column = rows[i].ranges[j].column - 1; + rows[i].ranges[j].lastColumn = rows[i].ranges[j].lastColumn - 1; + } else if (j == lastColumn && + rows[i].ranges[j] == rows[i].ranges[lastColumn]) { + rows[i].ranges[j] = null; + } else { + rows[i].ranges[j] = rows[i].ranges[j + 1]; + } + } + } + } + } + if (hyperlinks.count > 0) { + for (final Hyperlink link in hyperlinks.innerList) { + if (link._attachedType == ExcelHyperlinkAttachedType.range && + link._column >= columnIndex) { + link._column = link._column - columnCount; + } + } + } + for (int k = 1; k <= columnCount; k++) { + if (columns.count > 0) { + for (int z = 0; z < columns.count - 1; z++) { + if (columns[z].index >= columnIndex && + columns[z + 1] != null && + columns[z + 1].width != 0.0) { + columns[z].width = columns[z + 1].width; + } + } + final int lastCol = getLastColumn() + 1; + for (final Column col in columns.innerList) { + if (col.index >= lastCol) { + col.width = 0.0; + } + } + } + if (columns.count == 1 && columns[0].index == columnIndex) { + columns[0].width = 0.0; + } + } + } + + /// Maximum length of the password. + final int _maxPassWordLength = 255; + + /// Alogrithm name to protect/unprotect worksheet. + String _algorithmName; + + /// Random generated Salt for the sheet password. + List _saltValue; + + /// Spin count to loop the hash algorithm. + int _spinCount; + + /// Hash value to ensure the sheet protected password. + List _hashValue; + + /// Gets a value indicating whether worksheet is protected with password. + bool _isPasswordProtected = false; + + /// Gets protected options. Read-only. For sets protection options use "Protect" method. + ExcelSheetProtectionOption _innerProtection; + + ExcelSheetProtectionOption _prepareProtectionOptions( + ExcelSheetProtectionOption options) { + options.content = false; + return options; + } + + /// 16-bit hash value of the password. + int _isPassword; + + /// Represent the flag for sheet protection. + final List _flag = []; + + /// Default password hash value. + static const int _defPasswordConst = 52811; + + /// Protect the worksheet with specific protection options and password. + void protect(String password, [ExcelSheetProtectionOption options]) { + if (_isPasswordProtected) { + throw Exception( + 'Sheet is already protected, before use unprotect method'); + } + if (password == null) throw Exception('password'); + + if (password.length > _maxPassWordLength) { + throw Exception('Length of the password can\'t be more than ' + + _maxPassWordLength.toString()); + } + if (options == null) { + options = ExcelSheetProtectionOption(); + options.content = true; + options.lockedCells = true; + options.unlockedCells = true; + } + if (options.all == true) { + options.content = true; + options.objects = true; + options.scenarios = true; + options.formatCells = true; + options.formatColumns = true; + options.formatRows = true; + options.insertColumns = true; + options.insertRows = true; + options.insertHyperlinks = true; + options.deleteColumns = true; + options.deleteRows = true; + options.lockedCells = true; + options.sort = true; + options.useAutoFilter = true; + options.usePivotTableAndPivotChart = true; + options.unlockedCells = true; + } + + _prepareProtectionOptions(options); + _innerProtection = options; + + _flag.add(!options.content); + _flag.add(!options.objects); + _flag.add(!options.scenarios); + _flag.add(!options.formatCells); + _flag.add(!options.formatColumns); + _flag.add(!options.formatRows); + _flag.add(!options.insertColumns); + _flag.add(!options.insertRows); + _flag.add(!options.insertHyperlinks); + _flag.add(!options.deleteColumns); + _flag.add(!options.deleteRows); + _flag.add(!options.lockedCells); + _flag.add(!options.sort); + _flag.add(!options.useAutoFilter); + _flag.add(!options.usePivotTableAndPivotChart); + _flag.add(!options.unlockedCells); + _advancedSheetProtection(password); + final int usPassword = + (password.isNotEmpty) ? _getPasswordHash(password) : 1; + _isPassword = usPassword; + _isPasswordProtected = true; + } + + /// Protects the worksheet based on the Excel 2013. + void _advancedSheetProtection(String password) { + _algorithmName = _SecurityHelper._sha512Alogrithm; + _saltValue = _createSalt(16); + _spinCount = 500; + final Hash algorithm = _SecurityHelper._getAlgorithm(_algorithmName); + List arrPassword = utf8.encode(password).toList(); + arrPassword = _convertCodeUnitsToUnicodeByteArray(arrPassword); + List temp = _SecurityHelper._combineArray(_saltValue, arrPassword); + final List h0 = algorithm.convert(temp).bytes.toList(); + List hi = h0; + for (int iterator = 0; iterator < _spinCount; iterator++) { + final Uint8List int32Bytes = Uint8List(4) + ..buffer.asByteData().setInt32(0, iterator, Endian.big); + final List arrIterator = int32Bytes.reversed.toList(); + temp = _SecurityHelper._combineArray(hi, arrIterator); + temp = Uint8List.fromList(temp); + hi = algorithm.convert(temp).bytes.toList(); + } + _hashValue = hi; + } + + /// Creates random salt. + List _createSalt(int length) { + if (length <= 0) { + Exception('length'); + } + final List result = List(length); + final rnd = Random(Range._toOADate(DateTime.now()).toInt()); + // final rnd = Random(); + final int iMaxValue = _maxPassWordLength + 1; + + for (int i = 0; i < length; i++) { + result[i] = rnd.nextInt(iMaxValue); + } + return result; + } + + /// Returns hash value for the password string. + static int _getPasswordHash(String password) { + if (password == null) { + return 0; + } + int usHash = 0; + // ignore: prefer_final_locals + for (int iCharIndex = 0, len = password.length; + iCharIndex < len; + iCharIndex++) { + List bits = _getCharBits15(password[iCharIndex]); + bits = _rotateBits(bits, iCharIndex + 1); + final int curNumber = _getUInt16FromBits(bits); + usHash ^= curNumber; + } + return (usHash ^ password.length ^ _defPasswordConst); + } + + List _convertCodeUnitsToUnicodeByteArray(List codeUnits) { + final ByteBuffer buffer = Uint8List(codeUnits.length * 2).buffer; + final ByteData bdata = ByteData.view(buffer); + int pos = 0; + for (final int val in codeUnits) { + bdata.setInt16(pos, val, Endian.little); + pos += 2; + } + return bdata.buffer.asUint8List(); + } + + /// Converts character to 15 bits sequence + static List _getCharBits15(String char) { + final List arrResult = List(15); + final int usSource = char.codeUnitAt(0); + int curBit = 1; + for (int i = 0; i < 15; i++) { + arrResult[i] = ((usSource & curBit) == curBit); + curBit <<= 1; + } + + return arrResult; + } + + static List _rotateBits(List bits, int count) { + if (bits == null) throw Exception('bits'); + + if (bits.isEmpty) return bits; + + if (count < 0) throw Exception('Count can\'t be less than zero'); + + final List arrResult = List(bits.length); + // ignore: prefer_final_locals + for (int i = 0, len = bits.length; i < len; i++) { + final int newPos = (i + count) % len; + arrResult[newPos] = bits[i]; + } + return arrResult; + } + + /// Converts bits array to UInt16 value. + static int _getUInt16FromBits(List bits) { + if (bits == null) throw Exception('bits'); + + if (bits.length > 16) throw Exception("There can't be more than 16 bits"); + + int usResult = 0; + int curBit = 1; + // ignore: prefer_final_locals + for (int i = 0, len = bits.length; i < len; i++) { + if (bits[i]) usResult += curBit; + curBit <<= 1; + } + + return usResult; + } + + /// Represents Protection Attributes + final List _protectionAttributes = [ + 'sheet', + 'objects', + 'scenarios', + 'formatCells', + 'formatColumns', + 'formatRows', + 'insertColumns', + 'insertRows', + 'insertHyperlinks', + 'deleteColumns', + 'deleteRows', + 'selectLockedCells', + 'sort', + 'autoFilter', + 'pivotTables', + 'selectunlockedCells', + ]; + + /// Represents the defeault values for sheet Protection. + final List _defaultValues = [ + false, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true, + true, + false, + false, + ]; + + /// Clear the worksheet. + /// Returns width of the specified column in pixels. + int _getColumnWidthInPixels(int iColumnIndex) { + if (iColumnIndex > _book._maxColumnCount) { + iColumnIndex = _book._maxColumnCount; + } + + if (iColumnIndex < 1 || iColumnIndex > _book._maxColumnCount) { + throw Exception( + 'Value cannot be less 1 and greater than max column index.'); + } + + final double widthInChars = _innerGetColumnWidth(iColumnIndex); + return _columnWidthToPixels(widthInChars); + } + + /// Returns width from ColumnInfoRecord if there is corresponding ColumnInfoRecord or StandardWidth if not. + double _innerGetColumnWidth(int iColumn) { + if (iColumn < 1) { + throw Exception("iColumn can't be less then 1"); + } + Column column; + if (columns != null && columns.contains(iColumn)) { + column = columns.getColumn(iColumn); + } + + double dResult; + + if (column == null) { + dResult = _standardWidth; + } else { + dResult = column.width; + } + + return dResult; + } + + /// Converts the specified column width from points to pixels. + int _columnWidthToPixels(double widthInChars) { + final double dFileWidth = _book._widthToFileWidth(widthInChars); + return _book._fileWidthToPixels(dFileWidth).toInt(); + } + + /// Converts the specified column width from pixels to points. + double _pixelsToColumnWidth(int pixels) { + return _book._pixelsToWidth(pixels); + } + + /// Sets column width in pixels for the specified column. + void _setColumnWidthInPixels(int iColumn, int value, bool isBestFit) { + final double dColumnWidth = _pixelsToColumnWidth(value); + _setColumnWidth(iColumn, dColumnWidth, isBestFit); + } + + /// Sets column width for the specified column. + void _setColumnWidth(int iColumn, double value, bool isBestFit) { + if (iColumn < 1 || iColumn > _book._maxColumnCount) { + throw Exception( + 'Column index cannot be larger then 256 or less then one'); + } + final double iOldValue = _innerGetColumnWidth(iColumn); + if (iOldValue != value) { + Column colInfo; + if (iColumn < columns.count) { + colInfo = columns[iColumn]; + } + + if (colInfo == null) { + colInfo = columns.add(); + colInfo.index = iColumn; + colInfo.width = _standardWidth; + } + + if (value > 255) value = 255; + colInfo.width = value; + + // colInfo._isBestFit = isBestFit; + } + } + + // /// Converts width displayed by Excel to width that should be written into file. + // double _evaluateFileColumnWidth(int realWidth) { + // return _book._widthToFileWidth(realWidth / 256.0) * 256; + // } + /// Clear the worksheet. void _clear() { if (_rows != null) { diff --git a/packages/syncfusion_flutter_xlsio/lib/xlsio.dart b/packages/syncfusion_flutter_xlsio/lib/xlsio.dart index 2844d9160..02c2e7621 100644 --- a/packages/syncfusion_flutter_xlsio/lib/xlsio.dart +++ b/packages/syncfusion_flutter_xlsio/lib/xlsio.dart @@ -4,13 +4,14 @@ library xlsio; import 'dart:collection'; import 'dart:convert'; import 'dart:core'; -import 'dart:io'; -import 'dart:math' as math; +import 'dart:math'; +import 'dart:typed_data'; import 'dart:ui'; import 'package:archive/archive.dart'; import 'package:xml/xml.dart'; import 'package:image/image.dart' as img; +import 'package:crypto/crypto.dart'; import 'package:syncfusion_officecore/officecore.dart'; import 'package:intl/intl.dart'; import 'package:intl/number_symbols_data.dart'; @@ -39,14 +40,13 @@ part 'src/xlsio/formats/formats_collection.dart'; part 'src/xlsio/formats/format_parser.dart'; part 'src/xlsio/formats/format_section.dart'; part 'src/xlsio/formats/format_section_collection.dart'; -part 'src/xlsio/formats/format_tokens/_constants.dart'; -part 'src/xlsio/formats/format_tokens/_enums.dart'; -part 'src/xlsio/formats/format_tokens/_format_token_base.dart'; +part 'src/xlsio/formats/format_tokens/constants.dart'; +part 'src/xlsio/formats/format_tokens/enums.dart'; +part 'src/xlsio/formats/format_tokens/format_token_base.dart'; part 'src/xlsio/formats/format_tokens/am_pm_token.dart'; part 'src/xlsio/formats/format_tokens/character_token.dart'; part 'src/xlsio/formats/format_tokens/day_token.dart'; part 'src/xlsio/formats/format_tokens/decimal_point_token.dart'; -part 'src/xlsio/formats/format_tokens/digit_token.dart'; part 'src/xlsio/formats/format_tokens/fraction_token.dart'; part 'src/xlsio/formats/format_tokens/hour_24_token.dart'; part 'src/xlsio/formats/format_tokens/hour_token.dart'; @@ -55,9 +55,9 @@ part 'src/xlsio/formats/format_tokens/minute_token.dart'; part 'src/xlsio/formats/format_tokens/month_token.dart'; part 'src/xlsio/formats/format_tokens/second_token.dart'; part 'src/xlsio/formats/format_tokens/significant_digit_token.dart'; -part 'src/xlsio/formats/format_tokens/single_char_token.dart'; part 'src/xlsio/formats/format_tokens/unknown_token.dart'; part 'src/xlsio/formats/format_tokens/year_token.dart'; +part 'src/xlsio/general/autofit_manager.dart'; part 'src/xlsio/general/culture_info.dart'; part 'src/xlsio/general/enums.dart'; part 'src/xlsio/general/serialize_workbook.dart'; @@ -76,3 +76,7 @@ part 'src/xlsio/worksheet/worksheet.dart'; part 'src/xlsio/worksheet/worksheet_collection.dart'; part 'src/xlsio/cell_styles/style.dart'; part 'src/xlsio/cell_styles/cell_style_wrapper.dart'; +part 'src/xlsio/hyperlinks/hyperlink.dart'; +part 'src/xlsio/hyperlinks/hyperlink_collection.dart'; +part 'src/xlsio/security/excel_sheet_protection.dart'; +part 'src/xlsio/security/security_helper.dart'; diff --git a/packages/syncfusion_flutter_xlsio/pubspec.yaml b/packages/syncfusion_flutter_xlsio/pubspec.yaml index fb086ea65..575fdf9af 100644 --- a/packages/syncfusion_flutter_xlsio/pubspec.yaml +++ b/packages/syncfusion_flutter_xlsio/pubspec.yaml @@ -13,5 +13,6 @@ dependencies: archive: ^2.0.13 image: ^2.1.18 intl: ^0.16.1 + crypto: ^2.1.5 syncfusion_officecore: path: ../syncfusion_officecore 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 4af4473c9..34d837c6e 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/generated_syncfusion_localizations.dart +++ b/packages/syncfusion_localizations/lib/src/l10n/generated_syncfusion_localizations.dart @@ -49,9 +49,24 @@ class SfLocalizationsAf extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Werksweek'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'items'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Geen gebeure nie'; @@ -88,9 +103,66 @@ class SfLocalizationsAf extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'van'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Niks beplan nie. Tik om te skep.'; + @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. Ek'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. Ek'; + + @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 todayLabel => r'Vandag'; } @@ -131,9 +203,24 @@ class SfLocalizationsAm extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'የሥራ ሳምንት'; + @override + String get dhualhiLabel => r'ዱል ሂጃህ'; + + @override + String get dhualqiLabel => r'ዱ አል-ቂዳህ'; + @override String get itemsDataPagerLabel => r'ዕቃዎች'; + @override + String get jumada1Label => r'ጁማዳ አል-አወል'; + + @override + String get jumada2Label => r'ጁማዳ አል-ታኒ'; + + @override + String get muharramLabel => r'ሙሃረም'; + @override String get noEventsCalendarLabel => r'ክስተቶች የሉም'; @@ -170,9 +257,66 @@ class SfLocalizationsAm extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'የ'; + @override + String get rabi1Label => r'ራቢዕ አል-አወል'; + + @override + String get rabi2Label => r'ራቢዕ አል-ታኒ'; + + @override + String get rajabLabel => r'ራጃብ'; + + @override + String get ramadanLabel => r'ረመዳን'; + + @override + String get safarLabel => r'ሳፋር'; + @override String get scheduleViewNewEventLabel => 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 todayLabel => r'ዛሬ'; } @@ -199,7 +343,7 @@ class SfLocalizationsAr extends SfGlobalLocalizations { String get allowedViewTimelineDayLabel => r'يوم المخطط الزمني'; @override - String get allowedViewTimelineMonthLabel => r'الجدول الزمني شهر'; + String get allowedViewTimelineMonthLabel => r'شهر المخطط الزمني'; @override String get allowedViewTimelineWeekLabel => r'أسبوع المخطط الزمني'; @@ -213,9 +357,24 @@ class SfLocalizationsAr extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'أسبوع العمل'; + @override + String get dhualhiLabel => r'ذو الحجة'; + + @override + String get dhualqiLabel => r'ذو القعدة'; + @override String get itemsDataPagerLabel => r'العناصر'; + @override + String get jumada1Label => r'جمادى الاول'; + + @override + String get jumada2Label => r'جمادى الثانية'; + + @override + String get muharramLabel => r'شهر محرم'; + @override String get noEventsCalendarLabel => r'لا أحداث'; @@ -252,9 +411,67 @@ class SfLocalizationsAr extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'من'; + @override + String get rabi1Label => r'ربيع الأول'; + + @override + String get rabi2Label => r'ربيع الثاني'; + + @override + String get rajabLabel => r'رجب'; + + @override + String get ramadanLabel => r'رمضان'; + + @override + String get safarLabel => r'سفر'; + @override String get scheduleViewNewEventLabel => 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 todayLabel => r'اليوم'; } @@ -295,9 +512,24 @@ class SfLocalizationsAz extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'İş həftəsi'; + @override + String get dhualhiLabel => r'Zül-hiccə'; + + @override + String get dhualqiLabel => r'Zülqüda'; + @override String get itemsDataPagerLabel => r'maddələr'; + @override + String get jumada1Label => r'Jumada al-awal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Məhərrəm'; + @override String get noEventsCalendarLabel => r'Tədbir yoxdur'; @@ -335,10 +567,67 @@ class SfLocalizationsAz extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'of'; + @override + String get rabi1Label => r'Rəbiul-əvvəl'; + + @override + String get rabi2Label => r'Rabi ' "'" r'əl-thani'; + + @override + String get rajabLabel => r'Rəcəb'; + + @override + String get ramadanLabel => r'Ramazan'; + + @override + String get safarLabel => r'Səfər'; + @override String get scheduleViewNewEventLabel => r'Heç bir şey planlaşdırılmamışdır. Yaratmaq üçün vurun.'; + @override + String get shaabanLabel => r'Şəban'; + + @override + String get shawwalLabel => r'Şəvval'; + + @override + String get shortDhualhiLabel => r'Zül-H'; + + @override + String get shortDhualqiLabel => r'Zül-Q'; + + @override + String get shortJumada1Label => r'Jum. Mən'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. Mə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'Şa.'; + + @override + String get shortShawwalLabel => r'Şou.'; + @override String get todayLabel => r'Bu gün'; } @@ -379,9 +668,24 @@ class SfLocalizationsBe extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Працоўны тыдзень'; + @override + String get dhualhiLabel => r'Джу аль-Хіджа'; + + @override + String get dhualqiLabel => r'Ду аль-Кіда'; + @override String get itemsDataPagerLabel => r'прадметы'; + @override + String get jumada1Label => r'Джумада аль-аўваль'; + + @override + String get jumada2Label => r'Джумада аль-Тані'; + + @override + String get muharramLabel => r'Мухаррам'; + @override String get noEventsCalendarLabel => r'Ніякіх падзей'; @@ -392,7 +696,7 @@ class SfLocalizationsBe extends SfGlobalLocalizations { String get ofDataPagerLabel => r'з'; @override - String get pagesDataPagerLabel => r'старонак'; + String get pagesDataPagerLabel => r'старонкі'; @override String get pdfBookmarksLabel => r'Закладкі'; @@ -419,10 +723,67 @@ class SfLocalizationsBe extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'з'; + @override + String get rabi1Label => r'Рабі аль-Аўваль'; + + @override + String get rabi2Label => r'Рабі аль-Тані'; + + @override + String get rajabLabel => r'Раджаб'; + + @override + String get ramadanLabel => r'Рамадан'; + + @override + String get safarLabel => r'Сафар'; + @override String get scheduleViewNewEventLabel => 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 todayLabel => r'Сёння'; } @@ -464,9 +825,24 @@ class SfLocalizationsBg extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Работна седмица'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'елементи'; + @override + String get jumada1Label => r'Джумада ал-аввал'; + + @override + String get jumada2Label => r'Джумада ал-тани'; + + @override + String get muharramLabel => r'Мухарам'; + @override String get noEventsCalendarLabel => r'Няма събития'; @@ -504,15 +880,72 @@ class SfLocalizationsBg extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'на'; @override - String get scheduleViewNewEventLabel => - r'Нищо не е планирано. Докоснете, за да създадете.'; + String get rabi1Label => r'Раби ал-аввал'; @override - String get todayLabel => r'Днес'; -} + String get rabi2Label => r'Раби ал-тани'; -/// The translations for Bengali Bangla (`bn`). -class SfLocalizationsBn extends SfGlobalLocalizations { + @override + String get rajabLabel => r'Раджаб'; + + @override + String get ramadanLabel => r'Рамадан'; + + @override + String get safarLabel => r'Сафар'; + + @override + String get scheduleViewNewEventLabel => + 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'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'Saf.'; + + @override + String get shortShaabanLabel => r'Ша.'; + + @override + String get shortShawwalLabel => r'Шоу.'; + + @override + String get todayLabel => r'Днес'; +} + +/// The translations for Bengali Bangla (`bn`). +class SfLocalizationsBn extends SfGlobalLocalizations { /// Creating an argument constructor of SfLocalizationsBn class const SfLocalizationsBn({ String localeName = 'bn', @@ -547,9 +980,24 @@ class SfLocalizationsBn extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'কর্ম সপ্তাহ'; + @override + String get dhualhiLabel => r'ধূ আল-হিজাহ'; + + @override + String get dhualqiLabel => r'ধু আল-কিয়াদাহ'; + @override String get itemsDataPagerLabel => r'আইটেম'; + @override + String get jumada1Label => r'জুমদা আল-আউয়াল'; + + @override + String get jumada2Label => r'জুমদা আল থানি'; + + @override + String get muharramLabel => r'মোহাররম'; + @override String get noEventsCalendarLabel => r'কোন ইভেন্ট নেই'; @@ -572,7 +1020,7 @@ class SfLocalizationsBn extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'পৃষ্ঠায় যান'; @override - String get pdfInvalidPageNumberLabel => r'দয়া করে একটি বৈধ নম্বর লিখুন'; + String get pdfInvalidPageNumberLabel => r'দয়া করে একটি বৈধ সংখ্যা লিখুন'; @override String get pdfNoBookmarksLabel => r'কোনও বুকমার্ক পাওয়া যায় নি'; @@ -586,10 +1034,67 @@ class SfLocalizationsBn extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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 safarLabel => r'সাফার'; + @override String get scheduleViewNewEventLabel => 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 todayLabel => r'আজ'; } @@ -630,9 +1135,24 @@ class SfLocalizationsBs extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Radna sedmica'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'predmeta'; + @override + String get jumada1Label => r'Jumada al-avval'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Nema događaja'; @@ -669,10 +1189,67 @@ class SfLocalizationsBs extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'od'; + @override + String get rabi1Label => r'Rabi al-Avval'; + + @override + String get rabi2Label => r'Rabi ' "'" r'al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramazan'; + + @override + String get safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Ništa planirano. Dodirnite za stvaranje.'; + @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. 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'RAM.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + @override String get todayLabel => r'Danas'; } @@ -714,9 +1291,24 @@ class SfLocalizationsCa extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Setmana laboral'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'articles'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'No hi ha esdeveniments'; @@ -754,9 +1346,66 @@ class SfLocalizationsCa extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'de'; + @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à'; + + @override + String get safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Res previst. Toca per crear.'; + @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. Jo'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. Jo'; + + @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 todayLabel => r'Avui'; } @@ -798,9 +1447,24 @@ class SfLocalizationsCs extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Pracovní týden'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'položky'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Žádné události'; @@ -837,10 +1501,67 @@ class SfLocalizationsCs extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'z'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Nic nebylo plánováno. Klepnutím vytvoříte.'; + @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. Já'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. Já'; + + @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 todayLabel => r'Dnes'; } @@ -873,7 +1594,7 @@ class SfLocalizationsDa extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'Tidslinjeuge'; @override - String get allowedViewTimelineWorkWeekLabel => r'Arbejdsuge for tidslinje'; + String get allowedViewTimelineWorkWeekLabel => r'Tidslinje Arbejdsuge'; @override String get allowedViewWeekLabel => r'Uge'; @@ -881,9 +1602,24 @@ class SfLocalizationsDa extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Arbejdsuge'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'genstande'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Ingen begivenheder'; @@ -906,7 +1642,7 @@ class SfLocalizationsDa extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'Gå til side'; @override - String get pdfInvalidPageNumberLabel => r'Indtast venligst et gyldigt nummer'; + String get pdfInvalidPageNumberLabel => r'Indtast et gyldigt nummer'; @override String get pdfNoBookmarksLabel => r'Ingen bogmærker fundet'; @@ -920,10 +1656,67 @@ class SfLocalizationsDa extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'af'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Intet planlagt. Tryk for at oprette.'; + @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'Vædder.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + @override String get todayLabel => r'I dag'; } @@ -964,9 +1757,24 @@ class SfLocalizationsDe extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Arbeitswoche'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'Artikel'; + @override + String get jumada1Label => r'Jumada al-Awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Keine Ereignisse'; @@ -1004,10 +1812,67 @@ class SfLocalizationsDe extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'von'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Nichts geplant. Tippen Sie zum Erstellen auf.'; + @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. ich'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. ich'; + + @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 todayLabel => r'Heute'; } @@ -1049,9 +1914,24 @@ class SfLocalizationsEl extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Εβδομάδα εργασίας'; + @override + String get dhualhiLabel => r'Ντου αλ Χιτζάτζ'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'αντικείμενα'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Τζουμάδα αλ-θάνι'; + + @override + String get muharramLabel => r'Μουχάραμ'; + @override String get noEventsCalendarLabel => r'Δεν υπάρχουν εκδηλώσεις'; @@ -1089,10 +1969,67 @@ class SfLocalizationsEl extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'του'; + @override + String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + + @override + String get rabi2Label => r'Ραμπι ' "'" r'αλ-θανι'; + + @override + String get rajabLabel => r'Ρατζάμπ'; + + @override + String get ramadanLabel => r'Ραμαζάνι'; + + @override + String get safarLabel => r'Σαφάρι'; + @override String get scheduleViewNewEventLabel => r'Τίποτα δεν έχει προγραμματιστεί. Πατήστε για δημιουργία.'; + @override + String get shaabanLabel => r'Σααμπάν'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => r'Ντουλ-Χ'; + + @override + String get shortDhualqiLabel => r'Dhu' "'" r'l-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'Εμβολο.'; + + @override + String get shortSafarLabel => r'Σαφ.'; + + @override + String get shortShaabanLabel => r'Σα.'; + + @override + String get shortShawwalLabel => r'Σω.'; + @override String get todayLabel => r'Σήμερα'; } @@ -1133,9 +2070,24 @@ class SfLocalizationsEn extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Work Week'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'items'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'No events'; @@ -1172,9 +2124,66 @@ class SfLocalizationsEn extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'of'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Nothing planned. Tap to create.'; + @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. I'; + + @override + String get shortJumada2Label => r'Jum. 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'Ram.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + @override String get todayLabel => r'Today'; } @@ -1216,9 +2225,24 @@ class SfLocalizationsEs extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Semana de trabajo'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'artículos'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'No hay eventos'; @@ -1255,9 +2279,66 @@ class SfLocalizationsEs extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'de'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Nada planeado. Toque para crear.'; + @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. yo'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. yo'; + + @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 todayLabel => r'Hoy'; } @@ -1298,9 +2379,24 @@ class SfLocalizationsEt extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Töönädal'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'esemed'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Ühtegi üritust pole'; @@ -1338,57 +2434,129 @@ class SfLocalizationsEt extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'kohta'; @override - String get scheduleViewNewEventLabel => - r'Midagi pole plaanitud. Puudutage loomiseks.'; + String get rabi1Label => r'Rabi ' "'" r'al-awwal'; @override - String get todayLabel => r'Täna'; -} + String get rabi2Label => r'Rabi ' "'" r'al-thani'; -/// 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 rajabLabel => r'Rajab'; @override - String get allowedViewDayLabel => r'Eguna'; + String get ramadanLabel => r'Ramadaan'; @override - String get allowedViewMonthLabel => r'Hilabetea'; + String get safarLabel => r'Safar'; @override - String get allowedViewScheduleLabel => r'Ordutegia'; + String get scheduleViewNewEventLabel => + r'Midagi pole plaanitud. Puudutage loomiseks.'; @override - String get allowedViewTimelineDayLabel => r'Kronologia Eguna'; + String get shaabanLabel => r'Sha' "'" r'aban'; @override - String get allowedViewTimelineMonthLabel => r'Kronograma Hilabetea'; + String get shawwalLabel => r'Shawwal'; @override - String get allowedViewTimelineWeekLabel => r'Kronograma Astea'; + String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; @override - String get allowedViewTimelineWorkWeekLabel => r'Kronogramaren Lan Astea'; + String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get allowedViewWeekLabel => r'Astea'; + String get shortJumada1Label => r'Jum. Mina'; @override - String get allowedViewWorkWeekLabel => r'Lan Astea'; + String get shortJumada2Label => r'Jum. II'; @override - String get itemsDataPagerLabel => r'elementuak'; + String get shortMuharramLabel => r'Muh.'; @override - String get noEventsCalendarLabel => r'Ez dago gertaerarik'; + String get shortRabi1Label => r'Rabi. Mina'; @override - String get noSelectedDateCalendarLabel => r'Ez dago hautatutako datarik'; + 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 todayLabel => r'Täna'; +} + +/// 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 allowedViewDayLabel => r'Eguna'; + + @override + String get allowedViewMonthLabel => r'Hilabetea'; + + @override + String get allowedViewScheduleLabel => r'Ordutegia'; + + @override + String get allowedViewTimelineDayLabel => r'Kronologia Eguna'; + + @override + String get allowedViewTimelineMonthLabel => r'Kronograma Hilabetea'; + + @override + String get allowedViewTimelineWeekLabel => r'Kronologia Astea'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Kronogramaren Lan Astea'; + + @override + String get allowedViewWeekLabel => r'Astea'; + + @override + String get allowedViewWorkWeekLabel => r'Lan Astea'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + + @override + String get itemsDataPagerLabel => r'elementuak'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Ez dago gertaerarik'; + + @override + String get noSelectedDateCalendarLabel => r'Ez dago hautatutako datarik'; @override String get ofDataPagerLabel => r'de'; @@ -1421,10 +2589,67 @@ class SfLocalizationsEu extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'de'; + @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'Ramadana'; + + @override + String get safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Ezer ez dago aurreikusita. Sakatu sortzeko.'; + @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. Nik'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. Nik'; + + @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 todayLabel => r'Gaur'; } @@ -1465,9 +2690,24 @@ class SfLocalizationsFa extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'هفته کاری'; + @override + String get dhualhiLabel => r'ذو الحجه'; + + @override + String get dhualqiLabel => r'ذو القعده'; + @override String get itemsDataPagerLabel => r'موارد'; + @override + String get jumada1Label => r'جماد al الاول'; + + @override + String get jumada2Label => r'جمادا الثانی'; + + @override + String get muharramLabel => r'محرم'; + @override String get noEventsCalendarLabel => r'هیچ رویدادی وجود ندارد'; @@ -1504,10 +2744,67 @@ class SfLocalizationsFa extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'از'; + @override + String get rabi1Label => r'ربیع الاول'; + + @override + String get rabi2Label => r'ربیع الثانی'; + + @override + String get rajabLabel => r'راجب'; + + @override + String get ramadanLabel => r'ماه رمضان'; + + @override + String get safarLabel => r'صفر'; + @override String get scheduleViewNewEventLabel => 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 todayLabel => r'امروز'; } @@ -1548,9 +2845,24 @@ class SfLocalizationsFi extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Työviikko'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'kohteita'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Ei tapahtumia'; @@ -1587,9 +2899,66 @@ class SfLocalizationsFi extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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'Ramadan'; + + @override + String get safarLabel => r'Safari'; + @override String get scheduleViewNewEventLabel => - r'Ei mitään suunniteltua. Napauta luodaksesi.'; + r'Ei mitään suunniteltua. Napauta luoda.'; + + @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'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. Minä'; + + @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 todayLabel => r'Tänään'; @@ -1632,9 +3001,24 @@ class SfLocalizationsFil extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Linggo ng trabaho'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'mga item'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Walang mga kaganapan'; @@ -1672,9 +3056,66 @@ class SfLocalizationsFil extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'ng'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Walang plano. I-tap upang lumikha.'; + @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'Si 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 todayLabel => r'Ngayon'; } @@ -1717,19 +3158,34 @@ class SfLocalizationsFr extends SfGlobalLocalizations { String get allowedViewWorkWeekLabel => r'Semaine de travail'; @override - String get itemsDataPagerLabel => r'articles'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get noEventsCalendarLabel => r'Pas d' "'" r'événements'; + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - String get noSelectedDateCalendarLabel => r'Aucune date sélectionnée'; + String get itemsDataPagerLabel => r'articles'; @override - String get ofDataPagerLabel => r'de'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get pagesDataPagerLabel => r'pages'; + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Pas d' "'" r'événements'; + + @override + String get noSelectedDateCalendarLabel => r'Aucune date sélectionnée'; + + @override + String get ofDataPagerLabel => r'de'; + + @override + String get pagesDataPagerLabel => r'pages'; @override String get pdfBookmarksLabel => r'Favoris'; @@ -1756,9 +3212,66 @@ class SfLocalizationsFr extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'de'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Rien de prévu. Appuyez pour créer.'; + @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'Muh.'; + + @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'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + @override String get todayLabel => r'Aujourd' "'" r'hui'; } @@ -1800,9 +3313,24 @@ class SfLocalizationsGl extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Semana do Traballo'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'elementos'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Non hai eventos'; @@ -1840,9 +3368,66 @@ class SfLocalizationsGl extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'de'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Nada previsto. Toca para crear.'; + @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'Xum. Eu'; + + @override + String get shortJumada2Label => r'Xum. 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 todayLabel => r'Hoxe'; } @@ -1883,9 +3468,24 @@ class SfLocalizationsGu extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'વર્ક વીક'; + @override + String get dhualhiLabel => r'ધુ અલ-હિજ્જા'; + + @override + String get dhualqiLabel => r'ધુ અલ-કિયાદહ'; + @override String get itemsDataPagerLabel => r'વસ્તુઓ'; + @override + String get jumada1Label => r'જુમાદા અલ-અવવાલ'; + + @override + String get jumada2Label => r'જુમાદા અલ-થiની'; + + @override + String get muharramLabel => r'મુહરમ'; + @override String get noEventsCalendarLabel => r'કોઈ ઘટનાઓ નથી'; @@ -1922,10 +3522,67 @@ class SfLocalizationsGu extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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 safarLabel => r'સફર'; + @override String get scheduleViewNewEventLabel => 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 todayLabel => r'આજે'; } @@ -1966,9 +3623,24 @@ class SfLocalizationsHe extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'שבוע עבודה'; + @override + String get dhualhiLabel => r'דהו אל-חיג' "'" r'ה'; + + @override + String get dhualqiLabel => r'דהו אל-קי' "'" r'דה'; + @override String get itemsDataPagerLabel => r'פריטים'; + @override + String get jumada1Label => r'ג' "'" r'ומדה אל-עוואל'; + + @override + String get jumada2Label => r'ג' "'" r'ומדה אל-ת' "'" r'אני'; + + @override + String get muharramLabel => r'מוהרם'; + @override String get noEventsCalendarLabel => r'אין אירועים'; @@ -2005,9 +3677,66 @@ class SfLocalizationsHe extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'שֶׁל'; + @override + String get rabi1Label => r'ראבי אל-עוואל'; + + @override + String get rabi2Label => r'ראבי אל-ת' "'" r'אני'; + + @override + String get rajabLabel => r'רג' "'" r'אב'; + + @override + String get ramadanLabel => r'רמדאן'; + + @override + String get safarLabel => r'ספאר'; + @override String get scheduleViewNewEventLabel => 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'ג' "'" 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'Saf.'; + + @override + String get shortShaabanLabel => r'שא.'; + + @override + String get shortShawwalLabel => r'שו.'; + @override String get todayLabel => r'היום'; } @@ -2048,9 +3777,24 @@ class SfLocalizationsHi extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'कार्य सप्ताह'; + @override + String get dhualhiLabel => r'धू अल-हिज्जाह'; + + @override + String get dhualqiLabel => r'धू अल-कियूबाह'; + @override String get itemsDataPagerLabel => r'आइटम'; + @override + String get jumada1Label => r'जुमादा अल-अव्वल'; + + @override + String get jumada2Label => r'जुमादा अल-थानी'; + + @override + String get muharramLabel => r'मुहर्रम'; + @override String get noEventsCalendarLabel => r'कोई आयोजन नहीं'; @@ -2087,9 +3831,66 @@ class SfLocalizationsHi extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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 safarLabel => r'सफ़र'; + @override String get scheduleViewNewEventLabel => - r'कुछ भी योजनाबद्ध नहीं है। बनाने के लिए टैप करें।'; + r'कुछ भी प्लान नहीं किया। बनाने के लिए टैप करें।'; + + @override + String get shaabanLabel => r'Sha' "'" r'aban'; + + @override + String get shawwalLabel => r'शावाल'; + + @override + String get shortDhualhiLabel => r'Dhu' "'" r'l-एच'; + + @override + String get shortDhualqiLabel => r'Dhu' "'" r'l-क्यू'; + + @override + String get shortJumada1Label => r'Jum। मैं'; + + @override + String get shortJumada2Label => r'Jum। द्वितीय'; + + @override + String get shortMuharramLabel => r'Muh।'; + + @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 todayLabel => r'आज'; @@ -2131,9 +3932,24 @@ class SfLocalizationsHr extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Radni tjedan'; + @override + String get dhualhiLabel => r'Dhu al-Hidždža'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'predmeta'; + @override + String get jumada1Label => r'Džumada al-avval'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Nema događaja'; @@ -2159,7 +3975,7 @@ class SfLocalizationsHr extends SfGlobalLocalizations { String get pdfInvalidPageNumberLabel => r'Unesite valjani broj'; @override - String get pdfNoBookmarksLabel => r'Nije pronađena nijedna oznaka'; + String get pdfNoBookmarksLabel => r'Nisu pronađene oznake'; @override String get pdfPaginationDialogCancelLabel => r'OTKAZATI'; @@ -2171,8 +3987,65 @@ class SfLocalizationsHr extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'od'; @override - String get scheduleViewNewEventLabel => - r'Ništa planirano. Dodirnite za stvaranje.'; + 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'Ramazan'; + + @override + String get safarLabel => r'Safar'; + + @override + String get scheduleViewNewEventLabel => + r'Ništa planirano. Dodirnite za stvaranje.'; + + @override + String get shaabanLabel => r'Ša' "'" 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. 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 todayLabel => r'Danas'; @@ -2214,9 +4087,24 @@ class SfLocalizationsHu extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Munkahét'; + @override + String get dhualhiLabel => r'Dhu al-Hidzsáh'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'elemeket'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Nincs esemény'; @@ -2233,7 +4121,7 @@ class SfLocalizationsHu extends SfGlobalLocalizations { String get pdfBookmarksLabel => r'Könyvjelzők'; @override - String get pdfEnterPageNumberLabel => r'Írja be az oldalszámot'; + String get pdfEnterPageNumberLabel => r'Adja meg az oldalszámot'; @override String get pdfGoToPageLabel => r'Menj az oldalra'; @@ -2254,10 +4142,67 @@ class SfLocalizationsHu extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'nak,-nek'; + @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 safarLabel => r'Szafar'; + @override String get scheduleViewNewEventLabel => r'Semmi sem tervezett. Koppintson a létrehozáshoz.'; + @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'Szaf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + @override String get todayLabel => r'Ma'; } @@ -2281,7 +4226,7 @@ class SfLocalizationsHy extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Գրաֆիկ'; @override - String get allowedViewTimelineDayLabel => r'Elineամանակագրական օր'; + String get allowedViewTimelineDayLabel => r'Elineամանակացույցի օր'; @override String get allowedViewTimelineMonthLabel => r'Elineամանակացույցի ամիս'; @@ -2299,9 +4244,24 @@ class SfLocalizationsHy extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Աշխատանքային շաբաթ'; + @override + String get dhualhiLabel => r'Huու ալ-Հիջա'; + + @override + String get dhualqiLabel => r'Դհու ալ-Քիդա'; + @override String get itemsDataPagerLabel => r'իրեր'; + @override + String get jumada1Label => r'Umումադա ալ-վուալ'; + + @override + String get jumada2Label => r'Umումադա ալ-թանի'; + + @override + String get muharramLabel => r'Մուհարամ'; + @override String get noEventsCalendarLabel => r'Ոչ մի իրադարձություն'; @@ -2338,10 +4298,67 @@ class SfLocalizationsHy extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'ի'; + @override + String get rabi1Label => r'Ռաբի ալ-Աուվալ'; + + @override + String get rabi2Label => r'Ռաբի ալ-թանի'; + + @override + String get rajabLabel => r'Ռաջաբ'; + + @override + String get ramadanLabel => r'Ռամադան'; + + @override + String get safarLabel => r'Սաֆար'; + @override String get scheduleViewNewEventLabel => r'Ոչինչ նախատեսված չէ: Հպեք ՝ ստեղծելու համար:'; + @override + String get shaabanLabel => r'Շաաբան'; + + @override + String get shawwalLabel => r'Շավալ'; + + @override + String get shortDhualhiLabel => r'Դհուլ-Հ'; + + @override + String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + + @override + String get shortJumada1Label => r'Umում Ես'; + + @override + String get shortJumada2Label => r'Umում 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 todayLabel => r'Այսօր'; } @@ -2382,9 +4399,24 @@ class SfLocalizationsId extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Minggu Kerja'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'item'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Tidak ada acara'; @@ -2421,10 +4453,67 @@ class SfLocalizationsId extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'dari'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Tidak ada yang direncanakan. Ketuk untuk membuat.'; + @override + String get shaabanLabel => r'Sha' "'" 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 todayLabel => r'Hari ini'; } @@ -2457,7 +4546,7 @@ class SfLocalizationsIs extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'Tímalínuvika'; @override - String get allowedViewTimelineWorkWeekLabel => r'Viku tímalínu'; + String get allowedViewTimelineWorkWeekLabel => r'Vinnuvika tímalínu'; @override String get allowedViewWeekLabel => r'Vika'; @@ -2465,9 +4554,24 @@ class SfLocalizationsIs extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Vinnuvika'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'hlutir'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Engir viðburðir'; @@ -2504,9 +4608,66 @@ class SfLocalizationsIs extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'af'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => - r'Ekkert skipulagt. Pikkaðu til að búa til.'; + r'Ekkert skipulagt. Pikkaðu á til að búa til.'; + + @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'Muh.'; + + @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 todayLabel => r'Í dag'; @@ -2537,7 +4698,7 @@ class SfLocalizationsIt extends SfGlobalLocalizations { String get allowedViewTimelineMonthLabel => r'Mese della sequenza temporale'; @override - String get allowedViewTimelineWeekLabel => r'Timeline Week'; + String get allowedViewTimelineWeekLabel => r'Settimana temporale'; @override String get allowedViewTimelineWorkWeekLabel => r'Timeline Work Week'; @@ -2548,9 +4709,24 @@ class SfLocalizationsIt extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Settimana di lavoro'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'elementi'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Nessun evento'; @@ -2589,21 +4765,78 @@ class SfLocalizationsIt extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'di'; @override - String get scheduleViewNewEventLabel => - r'Niente in programma. Tocca per creare.'; + String get rabi1Label => r'Rabi ' "'" r'al-awwal'; @override - String get todayLabel => r'Oggi'; -} + String get rabi2Label => r'Rabi ' "'" r'al-thani'; -/// 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 rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadan'; + + @override + String get safarLabel => r'Safar'; + + @override + String get scheduleViewNewEventLabel => + r'Niente in programma. Tocca per creare.'; + + @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'Rabi. io'; + + @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 todayLabel => r'Oggi'; +} + +/// 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 allowedViewDayLabel => r'日'; @@ -2615,7 +4848,7 @@ class SfLocalizationsJa extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'スケジュール'; @override - String get allowedViewTimelineDayLabel => r'タイムラインデー'; + String get allowedViewTimelineDayLabel => r'タイムラインの日'; @override String get allowedViewTimelineMonthLabel => r'タイムライン月'; @@ -2624,7 +4857,7 @@ class SfLocalizationsJa extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'タイムラインウィーク'; @override - String get allowedViewTimelineWorkWeekLabel => r'タイムライン作業週'; + String get allowedViewTimelineWorkWeekLabel => r'タイムラインワークウィーク'; @override String get allowedViewWeekLabel => r'週間'; @@ -2632,14 +4865,29 @@ class SfLocalizationsJa extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'労働週'; + @override + String get dhualhiLabel => r'ズル・ヒッジャ'; + + @override + String get dhualqiLabel => r'ズル・カイダ'; + @override String get itemsDataPagerLabel => r'アイテム'; + @override + String get jumada1Label => r'ジュマーダアルアウワル'; + + @override + String get jumada2Label => r'ジュマーダッサーニ'; + + @override + String get muharramLabel => r'ムハッラム'; + @override String get noEventsCalendarLabel => r'イベントなし'; @override - String get noSelectedDateCalendarLabel => r'選択された日付なし'; + String get noSelectedDateCalendarLabel => r'日付が選択されていません'; @override String get ofDataPagerLabel => r'の'; @@ -2648,7 +4896,7 @@ class SfLocalizationsJa extends SfGlobalLocalizations { String get pagesDataPagerLabel => r'ページ'; @override - String get pdfBookmarksLabel => r'しおり'; + String get pdfBookmarksLabel => r'ブックマーク'; @override String get pdfEnterPageNumberLabel => r'ページ番号を入力してください'; @@ -2672,7 +4920,64 @@ class SfLocalizationsJa extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'の'; @override - String get scheduleViewNewEventLabel => r'予定はありません。タップして作成します。'; + String get rabi1Label => r'Rabi' "'" r'al-awwal'; + + @override + String get rabi2Label => r'ラビー・アルタニ'; + + @override + String get rajabLabel => r'ラジャブ'; + + @override + String get ramadanLabel => r'ラマダン'; + + @override + String get safarLabel => r'サファル'; + + @override + String get scheduleViewNewEventLabel => r'何も計画されていません。タップして作成します。'; + + @override + String get shaabanLabel => r'シャアバーン'; + + @override + String get shawwalLabel => r'Shawwal'; + + @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'Saf。'; + + @override + String get shortShaabanLabel => r'Sha。'; + + @override + String get shortShawwalLabel => r'ショー。'; @override String get todayLabel => r'今日'; @@ -2688,7 +4993,7 @@ class SfLocalizationsKa extends SfGlobalLocalizations { ); @override - String get allowedViewDayLabel => r'Დღეს'; + String get allowedViewDayLabel => r'Დღის'; @override String get allowedViewMonthLabel => r'თვე'; @@ -2714,11 +5019,26 @@ class SfLocalizationsKa extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Სამუშაო კვირა'; + @override + String get dhualhiLabel => r'დუ ალ-ჰიჯა'; + + @override + String get dhualqiLabel => r'დუ ალ-ქიდა'; + @override String get itemsDataPagerLabel => r'საგნები'; @override - String get noEventsCalendarLabel => r'არანაირი ღონისძიება'; + String get jumada1Label => r'ჯუმადა ალ-ავალი'; + + @override + String get jumada2Label => r'ჯუმადა ალ-ტანი'; + + @override + String get muharramLabel => r'მუჰარამი'; + + @override + String get noEventsCalendarLabel => r'არანაირი მოვლენა'; @override String get noSelectedDateCalendarLabel => r'არჩეული თარიღი არ არის'; @@ -2753,9 +5073,66 @@ class SfLocalizationsKa extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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 safarLabel => r'საფარი'; + @override String get scheduleViewNewEventLabel => - r'არაფერი დაგეგმილი. შეეხეთ შესაქმნელად.'; + 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 todayLabel => r'დღეს'; @@ -2797,9 +5174,24 @@ class SfLocalizationsKk extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Жұмыс аптасы'; + @override + String get dhualhiLabel => r'Зуль-Хиджа'; + + @override + String get dhualqiLabel => r'Зуль-әл-Қида'; + @override String get itemsDataPagerLabel => r'заттар'; + @override + String get jumada1Label => r'Джумада әл-аввал'; + + @override + String get jumada2Label => r'Джумада аль-тени'; + + @override + String get muharramLabel => r'Мухаррам'; + @override String get noEventsCalendarLabel => r'Іс-шаралар жоқ'; @@ -2836,10 +5228,67 @@ class SfLocalizationsKk extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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 safarLabel => r'Сафар'; + @override String get scheduleViewNewEventLabel => r'Ештеңе жоспарланбаған. Жасау үшін түртіңіз.'; + @override + String get shaabanLabel => r'Шаабан'; + + @override + String get shawwalLabel => r'Шаввал'; + + @override + String get shortDhualhiLabel => r'Зуль-Х'; + + @override + String get shortDhualqiLabel => r'Зуль-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'Шоу.'; + @override String get todayLabel => r'Бүгін'; } @@ -2880,9 +5329,24 @@ class SfLocalizationsKm extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'សប្តាហ៍ការងារ'; + @override + String get dhualhiLabel => r'ឌូអាល់ហីចា'; + + @override + String get dhualqiLabel => r'ឌូអាល់ឈីឌា'; + @override String get itemsDataPagerLabel => r'របស់របរ'; + @override + String get jumada1Label => r'ជូម៉ាដាអាល់អាល់វ៉ាល'; + + @override + String get jumada2Label => r'ចាមដាអាល់ - ថាវី'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'គ្មានព្រឹត្តិការណ៍'; @@ -2919,10 +5383,67 @@ class SfLocalizationsKm extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'នៃ'; + @override + String get rabi1Label => r'រ៉ាប៊ី ' "'" r'អាល់ - ដាស់'; + + @override + String get rabi2Label => r'រ៉ាប៊ីអាល់ - ថាវី'; + + @override + String get rajabLabel => r'រ៉ាបាប់'; + + @override + String get ramadanLabel => r'រ៉ាម៉ាដាន'; + + @override + String get safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'គ្មានអ្វីដែលបានគ្រោងទុក។ ប៉ះដើម្បីបង្កើត។'; + @override + String get shaabanLabel => r'សាអាបាន'; + + @override + String get shawwalLabel => r'Shawwal'; + + @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 todayLabel => r'ថ្ងៃនេះ'; } @@ -2970,10 +5491,29 @@ class SfLocalizationsKn extends SfGlobalLocalizations { String get allowedViewWorkWeekLabel => '\u{c95}\u{cc6}\u{cb2}\u{cb8}\u{ca6}\u{20}\u{cb5}\u{cbe}\u{cb0}'; + @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 itemsDataPagerLabel => '\u{cb5}\u{cb8}\u{ccd}\u{ca4}\u{cc1}\u{c97}\u{cb3}\u{cc1}'; + @override + String get jumada1Label => + '\u{c9c}\u{cc1}\u{cae}\u{ca1}\u{cbe}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{c85}\u{cb5}\u{ccd}\u{cb5}\u{cbe}\u{cb2}\u{ccd}'; + + @override + String get jumada2Label => + '\u{c9c}\u{cc1}\u{cae}\u{ca1}\u{cbe}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{ca5}\u{cbe}\u{ca8}\u{cbf}'; + + @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}'; @@ -3020,14 +5560,80 @@ class SfLocalizationsKn extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => '\u{ca8}'; @override - String get scheduleViewNewEventLabel => - '\u{caf}\u{cbe}\u{cb5}\u{cc1}\u{ca6}\u{ca8}\u{ccd}\u{ca8}\u{cc2}\u{20}\u{caf}\u{ccb}\u{c9c}\u{cbf}\u{cb8}\u{cbf}\u{cb2}\u{ccd}\u{cb2}\u{2e}\u{20}\u{cb0}\u{c9a}\u{cbf}\u{cb8}\u{cb2}\u{cc1}\u{20}\u{c9f}\u{ccd}\u{caf}\u{cbe}\u{caa}\u{ccd}\u{20}\u{cae}\u{cbe}\u{ca1}\u{cbf}\u{2e}'; + 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{cbe}\u{cb2}\u{ccd}'; @override - String get todayLabel => '\u{c87}\u{c82}\u{ca6}\u{cc1}'; -} + 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}'; -/// The translations for Korean (`ko`). + @override + String get rajabLabel => '\u{cb0}\u{cbe}\u{c9c}\u{cbe}\u{cac}\u{ccd}'; + + @override + String get ramadanLabel => '\u{cb0}\u{c82}\u{c9c}\u{cbe}\u{ca8}\u{ccd}'; + + @override + String get safarLabel => '\u{cb8}\u{cab}\u{cb0}\u{ccd}'; + + @override + String get scheduleViewNewEventLabel => + '\u{caf}\u{cbe}\u{cb5}\u{cc1}\u{ca6}\u{ca8}\u{ccd}\u{ca8}\u{cc2}\u{20}\u{caf}\u{ccb}\u{c9c}\u{cbf}\u{cb8}\u{cbf}\u{cb2}\u{ccd}\u{cb2}\u{2e}\u{20}\u{cb0}\u{c9a}\u{cbf}\u{cb8}\u{cb2}\u{cc1}\u{20}\u{c9f}\u{ccd}\u{caf}\u{cbe}\u{caa}\u{ccd}\u{20}\u{cae}\u{cbe}\u{ca1}\u{cbf}\u{2e}'; + + @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{cb9}\u{cc6}\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{cae}\u{ccd}\u{2e}\u{20}\u{ca8}\u{cbe}\u{ca8}\u{cc1}'; + + @override + String get shortJumada2Label => + '\u{c9c}\u{cae}\u{ccd}\u{2e}\u{20}\u{49}\u{49}'; + + @override + String get shortMuharramLabel => '\u{cae}\u{cc1}\u{cb9}\u{ccd}\u{2e}'; + + @override + String get shortRabi1Label => + '\u{cb0}\u{cac}\u{cbf}\u{2e}\u{20}\u{ca8}\u{cbe}\u{ca8}\u{cc1}'; + + @override + String get shortRabi2Label => '\u{cb0}\u{cac}\u{cbf}\u{2e}\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{cc1}\u{cb0}\u{c95}\u{ccd}\u{cb7}\u{cbf}\u{ca4}\u{2e}'; + + @override + String get shortShaabanLabel => '\u{cb6}\u{cbe}\u{2e}'; + + @override + String get shortShawwalLabel => '\u{cb6}\u{cbe}\u{2e}'; + + @override + String get todayLabel => '\u{c87}\u{c82}\u{ca6}\u{cc1}'; +} + +/// The translations for Korean (`ko`). class SfLocalizationsKo extends SfGlobalLocalizations { /// Creating an argument constructor of SfLocalizationsKo class const SfLocalizationsKo({ @@ -3063,9 +5669,24 @@ class SfLocalizationsKo extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'작업 주'; + @override + String get dhualhiLabel => r'두 알 히자'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'항목'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'무 하람'; + @override String get noEventsCalendarLabel => r'이벤트 없음'; @@ -3102,9 +5723,66 @@ class SfLocalizationsKo extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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 safarLabel => r'사 파르'; + @override String get scheduleViewNewEventLabel => r'계획된 것이 없습니다. 만들려면 탭하세요.'; + @override + String get shaabanLabel => r'샤아 반'; + + @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'음.'; + + @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'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'쇼.'; + @override String get todayLabel => r'오늘'; } @@ -3131,7 +5809,7 @@ class SfLocalizationsKy extends SfGlobalLocalizations { String get allowedViewTimelineDayLabel => r'Убакыт тилкеси күнү'; @override - String get allowedViewTimelineMonthLabel => r'Убакыт тилкесиндеги ай'; + String get allowedViewTimelineMonthLabel => r'Убакыт айы'; @override String get allowedViewTimelineWeekLabel => r'Timeline Week'; @@ -3145,9 +5823,24 @@ class SfLocalizationsKy extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Жумуш аптасы'; + @override + String get dhualhiLabel => r'Зуль-Хиджа'; + + @override + String get dhualqiLabel => r'Zhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'буюмдар'; + @override + String get jumada1Label => r'Жумада ал-аввал'; + + @override + String get jumada2Label => r'Жумада ал-тени'; + + @override + String get muharramLabel => r'Мухаррам'; + @override String get noEventsCalendarLabel => r'Иш-чара жок'; @@ -3185,10 +5878,67 @@ class SfLocalizationsKy extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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 safarLabel => r'Сафар'; + @override String get scheduleViewNewEventLabel => r'Эч нерсе пландаштырылган эмес. Түзүү үчүн таптап коюңуз.'; + @override + String get shaabanLabel => r'Шаабан'; + + @override + String get shawwalLabel => r'Шаввал'; + + @override + String get shortDhualhiLabel => r'Зуль-Х'; + + @override + String get shortDhualqiLabel => r'Zhu' "'" 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'Raj.'; + + @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 todayLabel => r'Бүгүн'; } @@ -3229,9 +5979,24 @@ class SfLocalizationsLo extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'ອາທິດເຮັດວຽກ'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'ລາຍການ'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'ບໍ່ມີເຫດການ'; @@ -3268,9 +6033,66 @@ class SfLocalizationsLo extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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'Ramadan'; + + @override + String get safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'ບໍ່ມີຫຍັງວາງແຜນ. ແຕະເພື່ອສ້າງ.'; + @override + String get shaabanLabel => r'ຊາ' "'" 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'ຈູມ. ຂ້ອຍ'; + + @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 todayLabel => r'ມື້​ນີ້'; } @@ -3311,9 +6133,24 @@ class SfLocalizationsLt extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Darbo savaitė'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'daiktų'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Jokių įvykių'; @@ -3350,10 +6187,67 @@ class SfLocalizationsLt extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'apie'; + @override + String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + + @override + String get rabi2Label => r'Rabi ' "'" r'al-thani'; + + @override + String get rajabLabel => r'Radžabas'; + + @override + String get ramadanLabel => r'Ramadanas'; + + @override + String get safarLabel => r'Safaras'; + @override String get scheduleViewNewEventLabel => r'Nieko neplanuota. Palieskite, kad sukurtumėte.'; + @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'Radž.'; + + @override + String get shortRamadanLabel => r'Avinas.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Ša.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + @override String get todayLabel => r'Šiandien'; } @@ -3394,9 +6288,24 @@ class SfLocalizationsLv extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Darba nedēļa'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'preces'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Nav notikumu'; @@ -3433,12 +6342,69 @@ class SfLocalizationsLv extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'gada'; + @override + String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + + @override + String get rabi2Label => r'Rabi ' "'" r'al-thani'; + + @override + String get rajabLabel => r'Radžabs'; + + @override + String get ramadanLabel => r'Ramadāns'; + + @override + String get safarLabel => r'Safārs'; + @override String get scheduleViewNewEventLabel => r'Nekas nav plānots. Pieskarieties, lai izveidotu.'; @override - String get todayLabel => r'Šodien'; + String get shaabanLabel => r'Šaabans'; + + @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. 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'Auns.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Ša.'; + + @override + String get shortShawwalLabel => r'Šovs.'; + + @override + String get todayLabel => r'Šodien'; } /// The translations for Macedonian (`mk`). @@ -3477,9 +6443,24 @@ class SfLocalizationsMk extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Работна недела'; + @override + String get dhualhiLabel => r'Huу ал-хиџа'; + + @override + String get dhualqiLabel => r'Huу ал-Кида'; + @override String get itemsDataPagerLabel => r'предмети'; + @override + String get jumada1Label => r'Umумада ал-аввал'; + + @override + String get jumada2Label => r'Umумада ал-тани'; + + @override + String get muharramLabel => r'Мухарем'; + @override String get noEventsCalendarLabel => r'Нема настани'; @@ -3516,10 +6497,67 @@ class SfLocalizationsMk extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'на'; + @override + String get rabi1Label => r'Раби ал-аввал'; + + @override + String get rabi2Label => r'Раби ал-тани'; + + @override + String get rajabLabel => r'Раџаб'; + + @override + String get ramadanLabel => r'Рамазан'; + + @override + String get safarLabel => r'Сафар'; + @override String get scheduleViewNewEventLabel => 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'Umум Јас'; + + @override + String get shortJumada2Label => r'Umум 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 todayLabel => r'Денес'; } @@ -3560,9 +6598,24 @@ class SfLocalizationsMl extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'പ്രവൃത്തി ആഴ്ച'; + @override + String get dhualhiLabel => r'ധു അൽ ഹിജ'; + + @override + String get dhualqiLabel => r'ധു അൽ ക്വിദ'; + @override String get itemsDataPagerLabel => r'ഇനങ്ങൾ'; + @override + String get jumada1Label => r'ജുമാദ അൽ അവ്വാൾ'; + + @override + String get jumada2Label => r'ജുമാദ അൽ താനി'; + + @override + String get muharramLabel => r'മുഹറം'; + @override String get noEventsCalendarLabel => r'ഇവന്റുകളൊന്നുമില്ല'; @@ -3599,10 +6652,67 @@ class SfLocalizationsMl extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'ന്റെ'; + @override + String get rabi1Label => r'റാബി അൽ അവൽ'; + + @override + String get rabi2Label => r'റാബി അൽ താനി'; + + @override + String get rajabLabel => r'രാജാബ്'; + + @override + String get ramadanLabel => r'റമദാൻ'; + + @override + String get safarLabel => r'സഫർ'; + @override String get scheduleViewNewEventLabel => 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'RAM.'; + + @override + String get shortSafarLabel => r'സേഫ്.'; + + @override + String get shortShaabanLabel => r'ഷാ.'; + + @override + String get shortShawwalLabel => r'ഷാ.'; + @override String get todayLabel => r'ഇന്ന്'; } @@ -3643,9 +6753,24 @@ class SfLocalizationsMn extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Ажлын долоо хоног'; + @override + String get dhualhiLabel => r'Зу аль-Хижжа'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'зүйлс'; + @override + String get jumada1Label => r'Жумада аль-аввал'; + + @override + String get jumada2Label => r'Жумада аль-тани'; + + @override + String get muharramLabel => r'Мухаррам'; + @override String get noEventsCalendarLabel => r'Арга хэмжээ алга'; @@ -3682,10 +6807,67 @@ class SfLocalizationsMn extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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 safarLabel => r'Сафар'; + @override String get scheduleViewNewEventLabel => r'Төлөвлөсөн зүйл алга. Үүсгэхийн тулд товшино уу.'; + @override + String get shaabanLabel => r'Шаабан'; + + @override + String get shawwalLabel => r'Шаввал'; + + @override + String get shortDhualhiLabel => r'Зул-Х'; + + @override + String get shortDhualqiLabel => r'Зул-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'Шоу.'; + @override String get todayLabel => r'Өнөөдөр'; } @@ -3718,7 +6900,7 @@ class SfLocalizationsMr extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'टाइमलाइन आठवडा'; @override - String get allowedViewTimelineWorkWeekLabel => r'टाइमलाइन कार्य सप्ताह'; + String get allowedViewTimelineWorkWeekLabel => r'टाइमलाइन कार्य आठवडा'; @override String get allowedViewWeekLabel => r'आठवडा'; @@ -3726,9 +6908,24 @@ class SfLocalizationsMr extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'कामाचा आठवडा'; + @override + String get dhualhiLabel => r'धु अल-हिज्जा'; + + @override + String get dhualqiLabel => r'धु अल-कायदा'; + @override String get itemsDataPagerLabel => r'आयटम'; + @override + String get jumada1Label => r'जुमादा अल-आव्वल'; + + @override + String get jumada2Label => r'जुमादा अल-थॅनी'; + + @override + String get muharramLabel => r'मुहर्रम'; + @override String get noEventsCalendarLabel => r'कार्यक्रम नाहीत'; @@ -3765,10 +6962,67 @@ class SfLocalizationsMr extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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 safarLabel => r'सफार'; + @override String get scheduleViewNewEventLabel => 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 todayLabel => r'आज'; } @@ -3809,9 +7063,24 @@ class SfLocalizationsMs extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Minggu Kerja'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'barang'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Tiada acara'; @@ -3849,52 +7118,124 @@ class SfLocalizationsMs extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'daripada'; @override - String get scheduleViewNewEventLabel => - r'Tidak ada yang dirancang. Ketik untuk membuat.'; + String get rabi1Label => r'Rabi ' "'" r'al-awwal'; @override - String get todayLabel => r'Hari ini'; -} + String get rabi2Label => r'Rabi ' "'" r'al-thani'; -/// 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 rajabLabel => r'Rajab'; @override - String get allowedViewDayLabel => r'နေ့'; + String get ramadanLabel => r'Ramadan'; @override - String get allowedViewMonthLabel => r'လ'; + String get safarLabel => r'Safar'; @override - String get allowedViewScheduleLabel => r'ဇယား'; + String get scheduleViewNewEventLabel => + r'Tidak ada yang dirancang. Ketik untuk membuat.'; @override - String get allowedViewTimelineDayLabel => r'အချိန်ဇယားနေ့'; + String get shaabanLabel => r'Sha' "'" r'aban'; @override - String get allowedViewTimelineMonthLabel => r'အချိန်ဇယားလ'; + String get shawwalLabel => r'Syawal'; @override - String get allowedViewTimelineWeekLabel => r'Timeline အပတ်'; + String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; @override - String get allowedViewTimelineWorkWeekLabel => r'Timeline အလုပ်ရက်သတ္တပတ်'; + String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get allowedViewWeekLabel => r'အပတ်'; + String get shortJumada1Label => r'Jum. Saya'; @override - String get allowedViewWorkWeekLabel => r'အလုပ်ရက်သတ္တပတ်'; + 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 todayLabel => r'Hari ini'; +} + +/// 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 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'Timeline အလုပ်ရက်သတ္တပတ်'; + + @override + String get allowedViewWeekLabel => r'အပတ်'; + + @override + String get allowedViewWorkWeekLabel => r'အလုပ်ရက်သတ္တပတ်'; + + @override + String get dhualhiLabel => r'Dhu Al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override String get itemsDataPagerLabel => r'ပစ္စည်းတွေ'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'ဖြစ်ရပ်များမရှိပါ'; @@ -3931,9 +7272,66 @@ class SfLocalizationsMy extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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'Ramadan'; + + @override + String get safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => 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 ။ ၂'; + + @override + String get shortMuharramLabel => r'Muh ။'; + + @override + String get shortRabi1Label => r'ရာဘီ။ ငါ'; + + @override + String get shortRabi2Label => r'ရာဘီ။ ၂'; + + @override + String get shortRajabLabel => r'Raj ။'; + + @override + String get shortRamadanLabel => r'ရမ်။'; + + @override + String get shortSafarLabel => r'Saf ။'; + + @override + String get shortShaabanLabel => r'Sha ။'; + + @override + String get shortShawwalLabel => r'Shaw ။'; + @override String get todayLabel => r'ဒီနေ့'; } @@ -3974,9 +7372,24 @@ class SfLocalizationsNb extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Arbeidsuke'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'gjenstander'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Ingen hendelser'; @@ -4013,10 +7426,67 @@ class SfLocalizationsNb extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'av'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Ingenting planlagt. Trykk for å opprette.'; + @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 todayLabel => r'I dag'; } @@ -4057,9 +7527,24 @@ class SfLocalizationsNe extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'कार्य सप्ताह'; + @override + String get dhualhiLabel => r'धु अल-हिज्जा'; + + @override + String get dhualqiLabel => r'धु अल Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'वस्तुहरू'; + @override + String get jumada1Label => r'जुमादा अल-अवाल'; + + @override + String get jumada2Label => r'जुमादा अल-थानी'; + + @override + String get muharramLabel => r'मुहर्रम'; + @override String get noEventsCalendarLabel => r'घटनाहरू छैनन्'; @@ -4097,10 +7582,67 @@ class SfLocalizationsNe extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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 safarLabel => r'सफार'; + @override String get scheduleViewNewEventLabel => r'केहि योजना गरिएको छैन। सिर्जना गर्न ट्याप गर्नुहोस्।'; + @override + String get shaabanLabel => r'Sha' "'" r'aban'; + + @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'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'सफ।'; + + @override + String get shortShaabanLabel => r'Sha।'; + + @override + String get shortShawwalLabel => r'श'; + @override String get todayLabel => r'आज'; } @@ -4141,9 +7683,24 @@ class SfLocalizationsNl extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Werkweek'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'artikelen'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-Thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Geen evenementen'; @@ -4172,7 +7729,7 @@ class SfLocalizationsNl extends SfGlobalLocalizations { String get pdfNoBookmarksLabel => r'Geen bladwijzers gevonden'; @override - String get pdfPaginationDialogCancelLabel => r'ANNULEER'; + String get pdfPaginationDialogCancelLabel => r'ANNULEREN'; @override String get pdfPaginationDialogOkLabel => r'OK'; @@ -4180,9 +7737,66 @@ class SfLocalizationsNl extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'van'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Niets gepland. Tik om te maken.'; + @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. ik'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. ik'; + + @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 todayLabel => r'Vandaag'; } @@ -4224,31 +7838,46 @@ class SfLocalizationsPa extends SfGlobalLocalizations { String get allowedViewWorkWeekLabel => r'ਕੰਮ ਦਾ ਹਫ਼ਤਾ'; @override - String get itemsDataPagerLabel => r'ਇਕਾਈ'; + String get dhualhiLabel => r'ਧੂ ਅਲ-ਹਿਜਾਜਾ'; @override - String get noEventsCalendarLabel => r'ਕੋਈ ਇਵੈਂਟ ਨਹੀਂ'; + String get dhualqiLabel => r'ਧੂ ਅਲ ਕਿਆਦਾਹ'; @override - String get noSelectedDateCalendarLabel => r'ਕੋਈ ਚੁਣੀ ਤਾਰੀਖ ਨਹੀਂ'; + String get itemsDataPagerLabel => r'ਇਕਾਈ'; @override - String get ofDataPagerLabel => r'ਦੇ'; + String get jumada1Label => r'ਜੁਮਾਦਾ ਅਲ-ਅਵਾਲ'; @override - String get pagesDataPagerLabel => r'ਪੰਨੇ'; + String get jumada2Label => r'ਜੁਮਦਾ ਅਲ-ਥਾਨੀ'; @override - String get pdfBookmarksLabel => r'ਬੁੱਕਮਾਰਕ'; + String get muharramLabel => r'ਮੁਹਰਰਾਮ'; @override - String get pdfEnterPageNumberLabel => r'ਪੇਜ ਨੰਬਰ ਦਰਜ ਕਰੋ'; + String get noEventsCalendarLabel => r'ਕੋਈ ਇਵੈਂਟ ਨਹੀਂ'; + + @override + String get noSelectedDateCalendarLabel => r'ਕੋਈ ਚੁਣੀ ਤਾਰੀਖ ਨਹੀਂ'; + + @override + String get ofDataPagerLabel => r'ਦੇ'; + + @override + String get pagesDataPagerLabel => r'ਪੰਨੇ'; + + @override + String get pdfBookmarksLabel => r'ਬੁੱਕਮਾਰਕ'; + + @override + String get pdfEnterPageNumberLabel => r'ਪੇਜ ਨੰਬਰ ਦਰਜ ਕਰੋ'; @override String get pdfGoToPageLabel => r'ਪੇਜ ਤੇ ਜਾਓ'; @override - String get pdfInvalidPageNumberLabel => r'ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਸਹੀ ਨੰਬਰ ਦਾਖਲ ਕਰੋ'; + String get pdfInvalidPageNumberLabel => r'ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਯੋਗ ਨੰਬਰ ਦਾਖਲ ਕਰੋ'; @override String get pdfNoBookmarksLabel => r'ਕੋਈ ਬੁੱਕਮਾਰਕ ਨਹੀਂ ਮਿਲਿਆ'; @@ -4262,10 +7891,67 @@ class SfLocalizationsPa extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => 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 safarLabel => r'ਸਫਾਰ'; + @override String get scheduleViewNewEventLabel => r'ਕੁਝ ਯੋਜਨਾਬੱਧ ਨਹੀਂ. ਬਣਾਉਣ ਲਈ ਟੈਪ ਕਰੋ.'; + @override + String get shaabanLabel => r'ਸ਼ਾਅਾਨ'; + + @override + String get shawwalLabel => r'ਸ਼ੋਵਾਲ'; + + @override + String get shortDhualhiLabel => r'ਧੂਲ-ਐਚ'; + + @override + String get shortDhualqiLabel => r'ਧੂਅਲ-ਕਿ 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'ਸ਼ਾ.'; + @override String get todayLabel => r'ਅੱਜ'; } @@ -4289,7 +7975,7 @@ class SfLocalizationsPl extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Harmonogram'; @override - String get allowedViewTimelineDayLabel => r'Dzień osi czasu'; + String get allowedViewTimelineDayLabel => r'Dzień na osi czasu'; @override String get allowedViewTimelineMonthLabel => r'Miesiąc osi czasu'; @@ -4306,9 +7992,24 @@ class SfLocalizationsPl extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Tydzień pracy'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'przedmiotów'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Brak wydarzeń'; @@ -4340,15 +8041,72 @@ class SfLocalizationsPl extends SfGlobalLocalizations { String get pdfPaginationDialogCancelLabel => r'ANULUJ'; @override - String get pdfPaginationDialogOkLabel => r'dobrze'; + String get pdfPaginationDialogOkLabel => r'ok'; @override String get pdfScrollStatusOfLabel => r'z'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Nic nie zostało zaplanowane. Dotknij, aby utworzyć.'; + @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. 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'Baran.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + @override String get todayLabel => r'Dzisiaj'; } @@ -4389,9 +8147,24 @@ class SfLocalizationsPs extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'کاري اونۍ'; + @override + String get dhualhiLabel => r'ذو الحجjah'; + + @override + String get dhualqiLabel => r'ذي القده'; + @override String get itemsDataPagerLabel => r'توکي'; + @override + String get jumada1Label => r'جمعه الاول'; + + @override + String get jumada2Label => r'جمعه الثاني'; + + @override + String get muharramLabel => r'محرم'; + @override String get noEventsCalendarLabel => r'هیڅ پیښه نده'; @@ -4429,10 +8202,67 @@ class SfLocalizationsPs extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'د'; + @override + String get rabi1Label => r'ربیع الاول'; + + @override + String get rabi2Label => r'ربیع الثاني'; + + @override + String get rajabLabel => r'رجب'; + + @override + String get ramadanLabel => r'رمضان'; + + @override + String get safarLabel => r'صفر'; + @override String get scheduleViewNewEventLabel => 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 todayLabel => r'نن'; } @@ -4474,9 +8304,24 @@ class SfLocalizationsPt extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Semana de trabalho'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'Itens'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-Thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Sem eventos'; @@ -4513,9 +8358,66 @@ class SfLocalizationsPt extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'do'; + @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ã'; + + @override + String get safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Nada planejado. Toque para criar.'; + @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 todayLabel => r'Hoje'; } @@ -4549,6 +8451,9 @@ class SfLocalizationsPtPt extends SfLocalizationsPt { @override String get pdfPaginationDialogOkLabel => r'Ok'; + + @override + String get shortRamadanLabel => r'Ram.'; } /// The translations for Romanian Moldavian Moldovan (`ro`). @@ -4588,9 +8493,24 @@ class SfLocalizationsRo extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Saptamana de lucru'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'obiecte'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Fără evenimente'; @@ -4628,12 +8548,69 @@ class SfLocalizationsRo extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'de'; + @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'Ramadanul'; + + @override + String get safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Nimic planificat. Atingeți pentru a crea.'; @override - String get todayLabel => r'Azi'; + 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'Berbec.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get todayLabel => r'Astăzi'; } /// The translations for Russian (`ru`). @@ -4664,7 +8641,7 @@ class SfLocalizationsRu extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'Хронология недели'; @override - String get allowedViewTimelineWorkWeekLabel => r'График работы за неделю'; + String get allowedViewTimelineWorkWeekLabel => r'График работы неделя'; @override String get allowedViewWeekLabel => r'Неделя'; @@ -4673,22 +8650,37 @@ class SfLocalizationsRu extends SfGlobalLocalizations { String get allowedViewWorkWeekLabel => r'Рабочая неделя'; @override - String get itemsDataPagerLabel => r'Предметы'; + String get dhualhiLabel => r'Зу аль-Хиджа'; @override - String get noEventsCalendarLabel => r'Нет событий'; + String get dhualqiLabel => r'Зу аль-Киада'; @override - String get noSelectedDateCalendarLabel => r'Дата не выбрана'; + String get itemsDataPagerLabel => r'Предметы'; @override - String get ofDataPagerLabel => r'из'; + String get jumada1Label => r'Джумада аль-Аввал'; @override - String get pagesDataPagerLabel => r'страницы'; + String get jumada2Label => r'Джумада аль-тани'; @override - String get pdfBookmarksLabel => r'Закладки'; + String get muharramLabel => r'Мухаррам'; + + @override + String get noEventsCalendarLabel => r'Нет событий'; + + @override + String get noSelectedDateCalendarLabel => r'Дата не выбрана'; + + @override + String get ofDataPagerLabel => r'из'; + + @override + String get pagesDataPagerLabel => r'страницы'; + + @override + String get pdfBookmarksLabel => r'Закладки'; @override String get pdfEnterPageNumberLabel => r'Введите номер страницы'; @@ -4707,15 +8699,72 @@ class SfLocalizationsRu extends SfGlobalLocalizations { String get pdfPaginationDialogCancelLabel => r'ОТМЕНА'; @override - String get pdfPaginationDialogOkLabel => r'хорошо'; + String get pdfPaginationDialogOkLabel => r'ОК'; @override String get pdfScrollStatusOfLabel => r'из'; + @override + String get rabi1Label => r'Раби аль-Аввал'; + + @override + String get rabi2Label => r'Раби аль-тани'; + + @override + String get rajabLabel => r'Раджаб'; + + @override + String get ramadanLabel => r'Рамадан'; + + @override + String get safarLabel => r'Сафар'; + @override String get scheduleViewNewEventLabel => 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'Saf.'; + + @override + String get shortShaabanLabel => r'Ша.'; + + @override + String get shortShawwalLabel => r'Шоу.'; + @override String get todayLabel => r'Cегодня'; } @@ -4730,7 +8779,7 @@ class SfLocalizationsSi extends SfGlobalLocalizations { ); @override - String get allowedViewDayLabel => r'දවස'; + String get allowedViewDayLabel => r'දිනය'; @override String get allowedViewMonthLabel => r'මාසික'; @@ -4756,9 +8805,24 @@ class SfLocalizationsSi extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'වැඩ සතිය'; + @override + String get dhualhiLabel => r'ධු අල් හිජ්ජා'; + + @override + String get dhualqiLabel => r'ධු අල්-කයිඩා'; + @override String get itemsDataPagerLabel => r'අයිතම'; + @override + String get jumada1Label => r'ජුමාඩා අල් අව්වාල්'; + + @override + String get jumada2Label => r'ජුමාඩා අල් තානි'; + + @override + String get muharramLabel => r'මුහාරම්'; + @override String get noEventsCalendarLabel => r'සිදුවීම් නොමැත'; @@ -4795,10 +8859,67 @@ class SfLocalizationsSi extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'වල'; + @override + String get rabi1Label => r'රබී අල් අව්වාල්'; + + @override + String get rabi2Label => r'රබී අල් තානි'; + + @override + String get rajabLabel => r'රාජාබ්'; + + @override + String get ramadanLabel => r'රාමදාන්'; + + @override + String get safarLabel => r'සෆාර්'; + @override String get scheduleViewNewEventLabel => 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'RAM.'; + + @override + String get shortSafarLabel => r'සේෆ්.'; + + @override + String get shortShaabanLabel => r'ෂා.'; + + @override + String get shortShawwalLabel => r'ෂෝ.'; + @override String get todayLabel => r'අද'; } @@ -4840,9 +8961,24 @@ class SfLocalizationsSk extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Pracovný týždeň'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'položky'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Žiadne udalosti'; @@ -4865,7 +9001,7 @@ class SfLocalizationsSk extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'Chod na stranu'; @override - String get pdfInvalidPageNumberLabel => r'Prosím vložte platné číslo'; + String get pdfInvalidPageNumberLabel => r'Prosím zadajte platné číslo'; @override String get pdfNoBookmarksLabel => r'Nenašli sa žiadne záložky'; @@ -4879,10 +9015,67 @@ class SfLocalizationsSk extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'z'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Nič plánované. Klepnutím vytvoríte.'; + @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. 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'Ram.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + @override String get todayLabel => r'Dnes'; } @@ -4923,9 +9116,24 @@ class SfLocalizationsSl extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Delovni teden'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'predmetov'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Ni dogodkov'; @@ -4962,10 +9170,67 @@ class SfLocalizationsSl extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'od'; + @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'Ramazan'; + + @override + String get safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Nič načrtovanega. Dotaknite se, če želite ustvariti.'; + @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. jaz'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. jaz'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Oven.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + @override String get todayLabel => r'Danes'; } @@ -5007,9 +9272,24 @@ class SfLocalizationsSq extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Java e Punës'; + @override + String get dhualhiLabel => r'Dhu al-Hixhah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'sende'; + @override + String get jumada1Label => r'Xhumada el-auval'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharrem'; + @override String get noEventsCalendarLabel => r'Asnjë ngjarje'; @@ -5047,10 +9327,67 @@ class SfLocalizationsSq extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'e'; + @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'Ramazani'; + + @override + String get safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Asgjë e planifikuar. Trokit për të krijuar.'; + @override + String get shaabanLabel => r'Sha' "'" r'aban'; + + @override + String get shawwalLabel => r'Shaval'; + + @override + String get shortDhualhiLabel => r'Dhul-H'; + + @override + String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + + @override + String get shortJumada1Label => r'Jum Une'; + + @override + String get shortJumada2Label => r'Jum II'; + + @override + String get shortMuharramLabel => r'Muh'; + + @override + String get shortRabi1Label => r'Rabi. Une'; + + @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 todayLabel => r'Sot'; } @@ -5074,7 +9411,7 @@ class SfLocalizationsSr extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Распоред'; @override - String get allowedViewTimelineDayLabel => r'Дан хронологије'; + String get allowedViewTimelineDayLabel => r'Дан временске оријентације'; @override String get allowedViewTimelineMonthLabel => r'Месец хронолошког следа'; @@ -5092,9 +9429,24 @@ class SfLocalizationsSr extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Радна недеља'; + @override + String get dhualhiLabel => r'Дху ал-Хиџа'; + + @override + String get dhualqiLabel => r'Дху ал-Ки' "'" r'дах'; + @override String get itemsDataPagerLabel => r'предмета'; + @override + String get jumada1Label => r'Јумада ал-аввал'; + + @override + String get jumada2Label => r'Јумада ал-тхани'; + + @override + String get muharramLabel => r'Мухаррам'; + @override String get noEventsCalendarLabel => r'Нема догађаја'; @@ -5126,15 +9478,72 @@ class SfLocalizationsSr extends SfGlobalLocalizations { String get pdfPaginationDialogCancelLabel => r'ПОНИШТИТИ, ОТКАЗАТИ'; @override - String get pdfPaginationDialogOkLabel => r'ок'; + String get pdfPaginationDialogOkLabel => r'У реду'; @override String get pdfScrollStatusOfLabel => r'од'; @override - String get scheduleViewNewEventLabel => + String get rabi1Label => r'Раби ' "'" r'ал-аввал'; + + @override + String get rabi2Label => r'Раби ' "'" r'ал-тхани'; + + @override + String get rajabLabel => r'Рајаб'; + + @override + String get ramadanLabel => r'Рамазан'; + + @override + String get safarLabel => r'Сафар'; + + @override + String get scheduleViewNewEventLabel => r'Ништа планирано. Додирните да бисте креирали.'; + @override + String get shaabanLabel => r'Сха' "'" r'абан'; + + @override + String get shawwalLabel => r'Схаввал'; + + @override + String get shortDhualhiLabel => r'Дху' "'" r'л-Х'; + + @override + String get shortDhualqiLabel => r'Дху' "'" 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 todayLabel => r'Данас'; } @@ -5185,9 +9594,24 @@ class SfLocalizationsSv extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Arbetsvecka'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'föremål'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Inga händelser'; @@ -5225,10 +9649,67 @@ class SfLocalizationsSv extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'av'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Inget planerat. Tryck för att skapa.'; + @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. Jag'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. Jag'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Bagge.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + @override String get todayLabel => r'I dag'; } @@ -5269,9 +9750,24 @@ class SfLocalizationsSw extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Wiki ya Kazi'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'vitu'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Hakuna matukio'; @@ -5300,7 +9796,7 @@ class SfLocalizationsSw extends SfGlobalLocalizations { String get pdfNoBookmarksLabel => r'Hakuna alamisho zilizopatikana'; @override - String get pdfPaginationDialogCancelLabel => r'GHAFU'; + String get pdfPaginationDialogCancelLabel => r'FUTA'; @override String get pdfPaginationDialogOkLabel => r'sawa'; @@ -5308,10 +9804,67 @@ class SfLocalizationsSw extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'ya'; + @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'Ramadhani'; + + @override + String get safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Hakuna kilichopangwa. Gonga ili uunde.'; + @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. Mimi'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. Mimi'; + + @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 todayLabel => r'Leo'; } @@ -5352,9 +9905,24 @@ class SfLocalizationsTa extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'வேலை வாரம்'; + @override + String get dhualhiLabel => r'து அல்-ஹிஜ்ஜா'; + + @override + String get dhualqiLabel => r'து அல்-கிதா'; + @override String get itemsDataPagerLabel => r'பொருட்களை'; + @override + String get jumada1Label => r'ஜுமதா அல்-அவ்வால்'; + + @override + String get jumada2Label => r'ஜுமதா அல்-தானி'; + + @override + String get muharramLabel => r'முஹர்ரம்'; + @override String get noEventsCalendarLabel => r'நிகழ்வுகள் இல்லை'; @@ -5391,10 +9959,67 @@ class SfLocalizationsTa extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'of'; + @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 safarLabel => r'சஃபர்'; + @override String get scheduleViewNewEventLabel => 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 todayLabel => r'இன்று'; } @@ -5435,9 +10060,24 @@ class SfLocalizationsTe extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'పని వారం'; + @override + String get dhualhiLabel => r'ధు అల్-హిజ్జా'; + + @override + String get dhualqiLabel => r'ధు అల్-ఖిదా'; + @override String get itemsDataPagerLabel => r'అంశాలు'; + @override + String get jumada1Label => r'జుమాడా అల్-అవ్వాల్'; + + @override + String get jumada2Label => r'జుమాడా అల్-తాని'; + + @override + String get muharramLabel => r'మొహర్రం'; + @override String get noEventsCalendarLabel => r'సంఘటనలు లేవు'; @@ -5475,10 +10115,67 @@ class SfLocalizationsTe extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'యొక్క'; + @override + String get rabi1Label => r'రబీ అల్-అవ్వాల్'; + + @override + String get rabi2Label => r'రబీ అల్-తాని'; + + @override + String get rajabLabel => r'రాజాబ్'; + + @override + String get ramadanLabel => r'రంజాన్'; + + @override + String get safarLabel => r'సఫర్'; + @override String get scheduleViewNewEventLabel => 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 todayLabel => r'ఈ రోజు'; } @@ -5519,9 +10216,24 @@ class SfLocalizationsTh extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'สัปดาห์การทำงาน'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'รายการ'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'มูฮาร์ราม'; + @override String get noEventsCalendarLabel => r'ไม่มีเหตุการณ์'; @@ -5559,7 +10271,65 @@ class SfLocalizationsTh extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'ของ'; @override - String get scheduleViewNewEventLabel => r'ไม่มีอะไรวางแผน แตะเพื่อสร้าง'; + 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 safarLabel => r'Safar'; + + @override + String get scheduleViewNewEventLabel => + 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'จุ๋ม. ผม'; + + @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 todayLabel => r'วันนี้'; @@ -5602,9 +10372,24 @@ class SfLocalizationsTl extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Linggo ng trabaho'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'mga item'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Walang mga kaganapan'; @@ -5642,9 +10427,66 @@ class SfLocalizationsTl extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'ng'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Walang plano. I-tap upang lumikha.'; + @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'Si 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 todayLabel => r'Ngayon'; } @@ -5686,9 +10528,24 @@ class SfLocalizationsTr extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Çalışma haftası'; + @override + String get dhualhiLabel => r'Zilhicce'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'öğeler'; + @override + String get jumada1Label => r'Jumada al-evvel'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharrem'; + @override String get noEventsCalendarLabel => r'Olay yok'; @@ -5725,10 +10582,67 @@ class SfLocalizationsTr extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'nın-nin'; + @override + String get rabi1Label => r'Rebiülevvel'; + + @override + String get rabi2Label => r'Rabi ' "'" r'al-thani'; + + @override + String get rajabLabel => r'Receb'; + + @override + String get ramadanLabel => r'Ramazan'; + + @override + String get safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Hiçbir şey planlanmadı. Oluşturmak için dokunun.'; + @override + String get shaabanLabel => r'Sha' "'" r'aban'; + + @override + String get shawwalLabel => r'Şevval'; + + @override + String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + + @override + String get shortDhualqiLabel => r'Zil-Q'; + + @override + String get shortJumada1Label => r'Jum. ben'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. ben'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Veri deposu.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + @override String get todayLabel => r'Bugün'; } @@ -5758,7 +10672,7 @@ class SfLocalizationsUk extends SfGlobalLocalizations { String get allowedViewTimelineMonthLabel => r'Місяць часової шкали'; @override - String get allowedViewTimelineWeekLabel => r'Тиждень часових шкал'; + String get allowedViewTimelineWeekLabel => r'Тиждень шкали часу'; @override String get allowedViewTimelineWorkWeekLabel => r'Хронологія робочого тижня'; @@ -5769,9 +10683,24 @@ class SfLocalizationsUk extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Робочий тиждень'; + @override + String get dhualhiLabel => r'Дху аль-Хіджа'; + + @override + String get dhualqiLabel => r'Дху аль-Кіда'; + @override String get itemsDataPagerLabel => r'предметів'; + @override + String get jumada1Label => r'Джумада аль-авваль'; + + @override + String get jumada2Label => r'Джумада аль-Тані'; + + @override + String get muharramLabel => r'Мухаррам'; + @override String get noEventsCalendarLabel => r'Жодних подій'; @@ -5803,98 +10732,227 @@ class SfLocalizationsUk extends SfGlobalLocalizations { String get pdfPaginationDialogCancelLabel => r'СКАСУВАТИ'; @override - String get pdfPaginationDialogOkLabel => r'гаразд'; + String get pdfPaginationDialogOkLabel => r'в порядку'; @override String get pdfScrollStatusOfLabel => r'з'; + @override + String get rabi1Label => r'Рабі аль-Ауваль'; + + @override + String get rabi2Label => r'Рабі аль-Тані'; + + @override + String get rajabLabel => r'Раджаб'; + + @override + String get ramadanLabel => r'Рамадан'; + + @override + String get safarLabel => r'Сафар'; + @override String get scheduleViewNewEventLabel => r'Нічого не планувалося. Торкніться, щоб створити.'; @override - String get todayLabel => 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 shaabanLabel => r'Шаабан'; + + @override + String get shawwalLabel => r'Шавваль'; + + @override + String get shortDhualhiLabel => r'Зул-Н'; + + @override + String get shortDhualqiLabel => r'Зул-Q'; + + @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'Saf.'; + + @override + String get shortShaabanLabel => r'Ша.'; + + @override + String get shortShawwalLabel => r'Шоу.'; + + @override + String get todayLabel => 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 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 dhualhiLabel => r'ذو الحجہ'; + + @override + String get dhualqiLabel => r'ذو الکعدہ'; + + @override + String get itemsDataPagerLabel => r'اشیاء'; + + @override + String get jumada1Label => r'جمعہ الاول'; + + @override + String get jumada2Label => r'جمعہ الثانی'; + + @override + String get muharramLabel => r'محرم'; + + @override + String get noEventsCalendarLabel => r'کوئی واقعات نہیں'; + + @override + String get noSelectedDateCalendarLabel => r'کوئی منتخب تاریخ'; + + @override + String get ofDataPagerLabel => r'کے'; + + @override + String get pagesDataPagerLabel => r'صفحات'; + + @override + String get pdfBookmarksLabel => r'بُک مارکس'; + + @override + String get pdfEnterPageNumberLabel => r'صفحہ نمبر درج کریں'; + + @override + String get pdfGoToPageLabel => r'صفحے پر جائیں'; + + @override + String get pdfInvalidPageNumberLabel => + r'براہ مہربانی ایک درست نمبر درج کریں'; + + @override + String get pdfNoBookmarksLabel => r'کوئی بُک مارکس نہیں ملا'; @override - String get allowedViewDayLabel => r'دن'; + String get pdfPaginationDialogCancelLabel => r'کینسل'; @override - String get allowedViewMonthLabel => r'مہینہ'; + String get pdfPaginationDialogOkLabel => r'ٹھیک ہے'; @override - String get allowedViewScheduleLabel => r'نظام الاوقات'; + String get pdfScrollStatusOfLabel => r'کے'; @override - String get allowedViewTimelineDayLabel => r'ٹائم لائن ڈے'; + String get rabi1Label => r'ربیع الاول'; @override - String get allowedViewTimelineMonthLabel => r'ٹائم لائن مہینہ'; + String get rabi2Label => r'ربیع الثانی'; @override - String get allowedViewTimelineWeekLabel => r'ٹائم لائن ہفتہ'; + String get rajabLabel => r'رجب'; @override - String get allowedViewTimelineWorkWeekLabel => r'ٹائم لائن ورک ویک'; + String get ramadanLabel => r'رمضان'; @override - String get allowedViewWeekLabel => r'ہفتہ'; + String get safarLabel => r'صفر'; @override - String get allowedViewWorkWeekLabel => r'کام کا ہفتہ'; + String get scheduleViewNewEventLabel => + r'کچھ بھی منصوبہ بند نہیں ہے۔ بنانے کے لئے تھپتھپائیں۔'; @override - String get itemsDataPagerLabel => r'اشیاء'; + String get shaabanLabel => r'شعبان'; @override - String get noEventsCalendarLabel => r'کوئی واقعات نہیں'; + String get shawwalLabel => r'شوال'; @override - String get noSelectedDateCalendarLabel => r'کوئی منتخب تاریخ'; + String get shortDhualhiLabel => r'ذوالحل'; @override - String get ofDataPagerLabel => r'کے'; + String get shortDhualqiLabel => r'ذوالق'; @override - String get pagesDataPagerLabel => 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 pdfScrollStatusOfLabel => r'کے'; + String get shortShaabanLabel => r'شا۔'; @override - String get scheduleViewNewEventLabel => - r'کچھ بھی منصوبہ بند نہیں ہے۔ بنانے کے لئے تھپتھپائیں۔'; + String get shortShawwalLabel => r'شا۔'; @override String get todayLabel => r'آج'; @@ -5937,9 +10995,24 @@ class SfLocalizationsUz extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Ish haftasi'; + @override + String get dhualhiLabel => r'Zul al-Hijja'; + + @override + String get dhualqiLabel => r'Zul al-Qida'; + @override String get itemsDataPagerLabel => r'buyumlar'; + @override + String get jumada1Label => r'Jumada al-avval'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Hech qanday tadbir yo' "'" r'q'; @@ -5977,10 +11050,67 @@ class SfLocalizationsUz extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'ning'; + @override + String get rabi1Label => r'Rabi ' "'" r'al-avval'; + + @override + String get rabi2Label => r'Rabi ' "'" r'al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramazon'; + + @override + String get safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Hech narsa rejalashtirilmagan. Yaratish uchun bosing.'; + @override + String get shaabanLabel => r'Sha' "'" r'bon'; + + @override + String get shawwalLabel => r'Shavvol'; + + @override + String get shortDhualhiLabel => r'Zulh'; + + @override + String get shortDhualqiLabel => r'Zul-Q'; + + @override + String get shortJumada1Label => r'Jum. Men'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. Men'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Ram.'; + + @override + String get shortSafarLabel => r'Xavfsiz'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shou.'; + @override String get todayLabel => r'Bugun'; } @@ -6007,7 +11137,7 @@ class SfLocalizationsVi extends SfGlobalLocalizations { String get allowedViewTimelineDayLabel => r'Ngày dòng thời gian'; @override - String get allowedViewTimelineMonthLabel => r'Dòng thời gian Tháng'; + String get allowedViewTimelineMonthLabel => r'Thời gian tháng'; @override String get allowedViewTimelineWeekLabel => r'Dòng thời gian tuần'; @@ -6021,9 +11151,24 @@ class SfLocalizationsVi extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Tuần làm việc'; + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'mặt hàng'; + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'Muharram'; + @override String get noEventsCalendarLabel => r'Không có sự kiện'; @@ -6043,7 +11188,7 @@ class SfLocalizationsVi extends SfGlobalLocalizations { String get pdfEnterPageNumberLabel => r'Nhập số trang'; @override - String get pdfGoToPageLabel => r'Đi đến trang'; + String get pdfGoToPageLabel => r'Đi tới trang'; @override String get pdfInvalidPageNumberLabel => r'Vui lòng nhập một số hợp lệ'; @@ -6060,9 +11205,66 @@ class SfLocalizationsVi extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'của'; + @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 safarLabel => r'Safar'; + @override String get scheduleViewNewEventLabel => r'Không có kế hoạch gì. Nhấn để tạo.'; + @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. Tôi'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Ờ.'; + + @override + String get shortRabi1Label => r'Rabi. Tôi'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Ram.'; + + @override + String get shortSafarLabel => r'Két sắt.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + @override String get todayLabel => r'Hôm nay'; } @@ -6103,9 +11305,24 @@ class SfLocalizationsZh extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'工作周'; + @override + String get dhualhiLabel => r'杜·希贾'; + + @override + String get dhualqiLabel => r'齐达'; + @override String get itemsDataPagerLabel => r'项目'; + @override + String get jumada1Label => r'朱马达·阿瓦尔'; + + @override + String get jumada2Label => r'朱马达·萨塔尼(Jumada al-thani)'; + + @override + String get muharramLabel => r'穆哈拉姆'; + @override String get noEventsCalendarLabel => r'没有活动'; @@ -6142,9 +11359,66 @@ class SfLocalizationsZh extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'的'; + @override + String get rabi1Label => r'拉比·阿瓦尔'; + + @override + String get rabi2Label => r'拉比阿尔塔尼'; + + @override + String get rajabLabel => r'拉贾卜'; + + @override + String get ramadanLabel => r'斋月'; + + @override + String get safarLabel => r'萨法尔'; + @override String get scheduleViewNewEventLabel => 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'um一世'; + + @override + String get shortJumada2Label => r'um 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'Saf。'; + + @override + String get shortShaabanLabel => r'沙。'; + + @override + String get shortShawwalLabel => r'肖'; + @override String get todayLabel => r'今天'; } @@ -6189,9 +11463,21 @@ class SfLocalizationsZhHant extends SfLocalizationsZh { @override String get allowedViewWorkWeekLabel => r'工作週'; + @override + String get dhualhiLabel => r'杜·希賈'; + + @override + String get dhualqiLabel => r'齊達'; + @override String get itemsDataPagerLabel => r'項目'; + @override + String get jumada1Label => r'朱馬達·阿瓦爾'; + + @override + String get jumada2Label => r'朱馬達·薩塔尼(Jumada al-thani)'; + @override String get noEventsCalendarLabel => r'沒有活動'; @@ -6216,8 +11502,29 @@ class SfLocalizationsZhHant extends SfLocalizationsZh { @override String get pdfNoBookmarksLabel => r'找不到書籤'; + @override + String get rabi1Label => r'拉比·阿瓦爾'; + + @override + String get rabi2Label => r'拉比阿爾塔尼'; + + @override + String get rajabLabel => r'拉賈卜'; + + @override + String get ramadanLabel => r'齋月'; + + @override + String get safarLabel => r'薩法爾'; + @override String get scheduleViewNewEventLabel => r'沒有計劃。點擊創建。'; + + @override + String get shawwalLabel => r'肖瓦爾'; + + @override + String get shortRamadanLabel => r'內存。'; } /// The translations for Chinese, as used in Hong Kong, using the Han script (`zh_Hant_HK`). @@ -6276,9 +11583,24 @@ class SfLocalizationsZu extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Isonto Lokusebenza'; + @override + String get dhualhiLabel => r'UDhu al-Hijjah'; + + @override + String get dhualqiLabel => r'UDhu al-Qi' "'" r'dah'; + @override String get itemsDataPagerLabel => r'izinto'; + @override + String get jumada1Label => r'Ijumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get muharramLabel => r'UMuharram'; + @override String get noEventsCalendarLabel => r'Ayikho imicimbi'; @@ -6315,10 +11637,67 @@ class SfLocalizationsZu extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'ye'; + @override + String get rabi1Label => r'URabi ' "'" r'al-awwal'; + + @override + String get rabi2Label => r'URabi ' "'" r'al-thani'; + + @override + String get rajabLabel => r'URajab'; + + @override + String get ramadanLabel => r'I-Ramadan'; + + @override + String get safarLabel => r'I-Safar'; + @override String get scheduleViewNewEventLabel => r'Akukho okuhleliwe. Thepha ukuze udale.'; + @override + String get shaabanLabel => r'Sha' "'" r'aban'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => r'UDhu' "'" r'l-H'; + + @override + String get shortDhualqiLabel => r'I-Dhu' "'" r'l-Q'; + + @override + String get shortJumada1Label => r'NgeSonto. Mina'; + + @override + String get shortJumada2Label => r'NgeSonto. II'; + + @override + String get shortMuharramLabel => r'UMuh.'; + + @override + String get shortRabi1Label => r'URabi. Mina'; + + @override + String get shortRabi2Label => r'URabi. II'; + + @override + String get shortRajabLabel => r'URaj.'; + + @override + String get shortRamadanLabel => r'URam.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + @override String get todayLabel => r'Namuhla'; } diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_af.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_af.arb index 111aca0ed..a24adb060 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_af.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_af.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Tydlynweek", "allowedViewTimelineWorkWeekLabel" : "Tydlyn-werkweek", "allowedViewTimelineMonthLabel" : "Tydlynmaand", -"todayLabel" : "Vandag" +"todayLabel" : "Vandag", +"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. Ek", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Ek", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 e2248818c..96fd15a44 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_am.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_am.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "የጊዜ ሰሌዳ ሳምንት", "allowedViewTimelineWorkWeekLabel" : "የጊዜ መስመር የስራ ሳምንት", "allowedViewTimelineMonthLabel" : "የጊዜ ሰሌዳ ወር", -"todayLabel" : "ዛሬ" +"todayLabel" : "ዛሬ", +"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" : "ዱል-ኤች" } \ 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 f6a235876..bf093da4e 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ar.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ar.arb @@ -21,6 +21,30 @@ "allowedViewTimelineDayLabel" : "يوم المخطط الزمني", "allowedViewTimelineWeekLabel" : "أسبوع المخطط الزمني", "allowedViewTimelineWorkWeekLabel" : "أسبوع العمل المخطط الزمني", -"allowedViewTimelineMonthLabel" : "الجدول الزمني شهر", -"todayLabel" : "اليوم" +"allowedViewTimelineMonthLabel" : "شهر المخطط الزمني", +"todayLabel" : "اليوم", +"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" : "ذو الحجة" } \ 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 e51310a6e..21e17d5e3 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_az.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_az.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Həyat həftəsi", "allowedViewTimelineWorkWeekLabel" : "İş qrafiki", "allowedViewTimelineMonthLabel" : "Təqvim ayı", -"todayLabel" : "Bu gün" +"todayLabel" : "Bu gün", +"muharramLabel" : "Məhərrəm", +"safarLabel" : "Səfər", +"rabi1Label" : "Rəbiul-əvvəl", +"rabi2Label" : "Rabi 'əl-thani", +"jumada1Label" : "Jumada al-awal", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "Rəcəb", +"shaabanLabel" : "Şəban", +"ramadanLabel" : "Ramazan", +"shawwalLabel" : "Şəvval", +"dhualqiLabel" : "Zülqüda", +"dhualhiLabel" : "Zül-hiccə", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Mən", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Mən", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Şa.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Şou.", +"shortDhualqiLabel" : "Zül-Q", +"shortDhualhiLabel" : "Zül-H" } \ 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 26cf32763..83884ee7f 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_be.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_be.arb @@ -3,7 +3,7 @@ "noEventsCalendarLabel" : "Ніякіх падзей", "scheduleViewNewEventLabel" : "Нічога не планавалася. Націсніце, каб стварыць.", "ofDataPagerLabel" : "з", -"pagesDataPagerLabel" : "старонак", +"pagesDataPagerLabel" : "старонкі", "itemsDataPagerLabel" : "прадметы", "pdfBookmarksLabel" : "Закладкі", "pdfNoBookmarksLabel" : "Закладак не знойдзена", @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Тыдзень тэрмінаў", "allowedViewTimelineWorkWeekLabel" : "Графік працоўнага тыдня", "allowedViewTimelineMonthLabel" : "Месяц тэрмінаў", -"todayLabel" : "Сёння" +"todayLabel" : "Сёння", +"muharramLabel" : "Мухаррам", +"safarLabel" : "Сафар", +"rabi1Label" : "Рабі аль-Аўваль", +"rabi2Label" : "Рабі аль-Тані", +"jumada1Label" : "Джумада аль-аўваль", +"jumada2Label" : "Джумада аль-Тані", +"rajabLabel" : "Раджаб", +"shaabanLabel" : "Шаабан", +"ramadanLabel" : "Рамадан", +"shawwalLabel" : "Шаўваль", +"dhualqiLabel" : "Ду аль-Кіда", +"dhualhiLabel" : "Джу аль-Хіджа", +"shortMuharramLabel" : "М-м-м.", +"shortSafarLabel" : "Саф.", +"shortRabi1Label" : "Рабі. Я", +"shortRabi2Label" : "Рабі. II", +"shortJumada1Label" : "Jum. Я", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Радж.", +"shortShaabanLabel" : "Ша.", +"shortRamadanLabel" : "Баран.", +"shortShawwalLabel" : "Шоу.", +"shortDhualqiLabel" : "Зул-К", +"shortDhualhiLabel" : "Зул-Н" } \ 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 3719f3460..03f3582f4 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bg.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bg.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Седмица на времевата скала", "allowedViewTimelineWorkWeekLabel" : "Работна седмица на хронологията", "allowedViewTimelineMonthLabel" : "Месец на хронологията", -"todayLabel" : "Днес" +"todayLabel" : "Днес", +"muharramLabel" : "Мухарам", +"safarLabel" : "Сафар", +"rabi1Label" : "Раби ал-аввал", +"rabi2Label" : "Раби ал-тани", +"jumada1Label" : "Джумада ал-аввал", +"jumada2Label" : "Джумада ал-тани", +"rajabLabel" : "Раджаб", +"shaabanLabel" : "Шаабан", +"ramadanLabel" : "Рамадан", +"shawwalLabel" : "Шаввал", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Ммм.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Раби. Аз", +"shortRabi2Label" : "Раби. II", +"shortJumada1Label" : "Jum. Аз", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Радж.", +"shortShaabanLabel" : "Ша.", +"shortRamadanLabel" : "Рам.", +"shortShawwalLabel" : "Шоу.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 3afcb81d5..8d5489653 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bn.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bn.arb @@ -10,7 +10,7 @@ "pdfScrollStatusOfLabel" : "এর", "pdfGoToPageLabel" : "পৃষ্ঠায় যান", "pdfEnterPageNumberLabel" : "পৃষ্ঠা নম্বর লিখুন", -"pdfInvalidPageNumberLabel" : "দয়া করে একটি বৈধ নম্বর লিখুন", +"pdfInvalidPageNumberLabel" : "দয়া করে একটি বৈধ সংখ্যা লিখুন", "pdfPaginationDialogOkLabel" : "ঠিক আছে", "pdfPaginationDialogCancelLabel" : "বাতিল", "allowedViewDayLabel" : "দিন", @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "টাইমলাইন সপ্তাহ", "allowedViewTimelineWorkWeekLabel" : "সময়রেখা কর্ম সপ্তাহ", "allowedViewTimelineMonthLabel" : "টাইমলাইন মাস", -"todayLabel" : "আজ" +"todayLabel" : "আজ", +"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" : "ধুল-এইচ" } \ 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 684eaac73..59672f92a 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bs.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bs.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Nedelja vremenske trake", "allowedViewTimelineWorkWeekLabel" : "Timeline Work Week", "allowedViewTimelineMonthLabel" : "Mjesec vremenske trake", -"todayLabel" : "Danas" +"todayLabel" : "Danas", +"muharramLabel" : "Muharram", +"safarLabel" : "Safar", +"rabi1Label" : "Rabi al-Avval", +"rabi2Label" : "Rabi 'al-thani", +"jumada1Label" : "Jumada al-avval", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "Rajab", +"shaabanLabel" : "Sha'aban", +"ramadanLabel" : "Ramazan", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Ja", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Ja", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 8ab2fefd5..978c79e66 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ca.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ca.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Setmana del cronograma", "allowedViewTimelineWorkWeekLabel" : "Setmana del treball de cronologia", "allowedViewTimelineMonthLabel" : "Mes de cronologia", -"todayLabel" : "Avui" +"todayLabel" : "Avui", +"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" : "Ramadà", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Jo", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Jo", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 c6a8dd1b9..df164c4e1 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_cs.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_cs.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Časová osa týden", "allowedViewTimelineWorkWeekLabel" : "Pracovní týden na časové ose", "allowedViewTimelineMonthLabel" : "Měsíc časové osy", -"todayLabel" : "Dnes" +"todayLabel" : "Dnes", +"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" : "Ramadán", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Já", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Já", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 23a0e7c71..2933d8815 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_da.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_da.arb @@ -10,7 +10,7 @@ "pdfScrollStatusOfLabel" : "af", "pdfGoToPageLabel" : "Gå til side", "pdfEnterPageNumberLabel" : "Indtast sidenummer", -"pdfInvalidPageNumberLabel" : "Indtast venligst et gyldigt nummer", +"pdfInvalidPageNumberLabel" : "Indtast et gyldigt nummer", "pdfPaginationDialogOkLabel" : "Okay", "pdfPaginationDialogCancelLabel" : "AFBESTILLE", "allowedViewDayLabel" : "Dag", @@ -20,7 +20,31 @@ "allowedViewScheduleLabel" : "Tidsplan", "allowedViewTimelineDayLabel" : "Tidslinjedag", "allowedViewTimelineWeekLabel" : "Tidslinjeuge", -"allowedViewTimelineWorkWeekLabel" : "Arbejdsuge for tidslinje", +"allowedViewTimelineWorkWeekLabel" : "Tidslinje Arbejdsuge", "allowedViewTimelineMonthLabel" : "Tidslinjemåned", -"todayLabel" : "I dag" +"todayLabel" : "I dag", +"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" : "Vædder.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 698a9366b..e3bac174f 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Timeline-Woche", "allowedViewTimelineWorkWeekLabel" : "Timeline Work Week", "allowedViewTimelineMonthLabel" : "Timeline-Monat", -"todayLabel" : "Heute" +"todayLabel" : "Heute", +"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. ich", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. ich", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 70a441a8d..44ecbbdd9 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_el.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_el.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Εβδομάδα χρονοδιαγράμματος", "allowedViewTimelineWorkWeekLabel" : "Εβδομάδα εργασίας χρονοδιαγράμματος", "allowedViewTimelineMonthLabel" : "Χρονοδιάγραμμα Μήνας", -"todayLabel" : "Σήμερα" +"todayLabel" : "Σήμερα", +"muharramLabel" : "Μουχάραμ", +"safarLabel" : "Σαφάρι", +"rabi1Label" : "Rabi 'al-awwal", +"rabi2Label" : "Ραμπι 'αλ-θανι", +"jumada1Label" : "Jumada al-awwal", +"jumada2Label" : "Τζουμάδα αλ-θάνι", +"rajabLabel" : "Ρατζάμπ", +"shaabanLabel" : "Σααμπάν", +"ramadanLabel" : "Ραμαζάνι", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Ντου αλ Χιτζάτζ", +"shortMuharramLabel" : "Μουχ.", +"shortSafarLabel" : "Σαφ.", +"shortRabi1Label" : "Ράμπι. Εγώ", +"shortRabi2Label" : "Ράμπι. ΙΙ", +"shortJumada1Label" : "Τζαμ. Εγώ", +"shortJumada2Label" : "Τζαμ. ΙΙ", +"shortRajabLabel" : "Κυριαρχία.", +"shortShaabanLabel" : "Σα.", +"shortRamadanLabel" : "Εμβολο.", +"shortShawwalLabel" : "Σω.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Ντουλ-Χ" } \ 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 5c884f594..ed358df6f 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.arb @@ -6,140 +6,284 @@ }, "noEventsCalendarLabel": "No events", - "@noEventsCalendarLabel": { + "@noEventsCalendarLabel": { "description": "Describes the events in calendar widget", "type": "text" }, "scheduleViewNewEventLabel": "Nothing planned. Tap to create.", - "@scheduleViewNewEventLabel": { + "@scheduleViewNewEventLabel": { "description": "Describes the display date in a calendar widget", "type": "text" }, "ofDataPagerLabel": "of", - "@ofDataPagerLabel": { + "@ofDataPagerLabel": { "description": "Label that is displayed in the information panel of DataPager to represent the currently selected page in number of pages.", "type": "text" }, "pagesDataPagerLabel": "pages", - "@pagesDataPagerLabel": { + "@pagesDataPagerLabel": { "description": "Label that is displayed in the information panel of DataPager to represent the currently selected page in number of pages.", "type": "text" }, "itemsDataPagerLabel": "items", - "@itemsDataPagerLabel": { + "@itemsDataPagerLabel": { "description": "Label that is displayed in the information panel of DataPager to represent the number of items.", "type": "text" }, "pdfBookmarksLabel": "Bookmarks", - "@pdfBookmarksLabel": { + "@pdfBookmarksLabel": { "description": "Label that is displayed in the bookmark view header of PdfViewer.", "type": "text" }, "pdfNoBookmarksLabel": "No bookmarks found", - "@pdfNoBookmarksLabel": { + "@pdfNoBookmarksLabel": { "description": "Label that is displayed in the bookmark view of PdfViewer when there is no bookmark found in loaded PDF document.", "type": "text" }, "pdfScrollStatusOfLabel": "of", - "@pdfScrollStatusOfLabel": { + "@pdfScrollStatusOfLabel": { "description": "Label that represents the `of` word in the scroll status of PdfViewer.", "type": "text" }, "pdfGoToPageLabel": "Go to page", - "@pdfGoToPageLabel": { + "@pdfGoToPageLabel": { "description": "Label that is displayed in the pagination dialog header of PdfViewer", "type": "text" }, "pdfEnterPageNumberLabel": "Enter page number", - "@pdfEnterPageNumberLabel": { + "@pdfEnterPageNumberLabel": { "description": "Label that is displayed in the text field of pagination dialog in the PdfViewer.", "type": "text" }, "pdfInvalidPageNumberLabel": "Please enter a valid number", - "@pdfInvalidPageNumberLabel": { + "@pdfInvalidPageNumberLabel": { "description": "Label that is displayed in the pagination dialog of PdfViewer when an invalid number is entered in the text field.", "type": "text" }, "pdfPaginationDialogOkLabel": "OK", - "@pdfPaginationDialogOkLabel": { + "@pdfPaginationDialogOkLabel": { "description": "Label that is displayed in the pagination dialog of PdfViewer to represent the OK confirmation button.", "type": "text" }, "pdfPaginationDialogCancelLabel": "CANCEL", - "@pdfPaginationDialogCancelLabel": { + "@pdfPaginationDialogCancelLabel": { "description": "Label that is displayed in the pagination dialog of PdfViewer to represent the CANCEL confirmation button.", "type": "text" }, "allowedViewDayLabel": "Day", - "@allowedViewDayLabel": { + "@allowedViewDayLabel": { "description": "Label that is displayed in the calendar header view when allowed views have calendar day view.", "type": "text" }, "allowedViewWeekLabel": "Week", - "@allowedViewWeekLabel": { + "@allowedViewWeekLabel": { "description": "Label that is displayed in the calendar header view when allowed views have calendar week view.", "type": "text" }, "allowedViewWorkWeekLabel": "Work Week", - "@allowedViewWorkWeekLabel": { + "@allowedViewWorkWeekLabel": { "description": "Label that is displayed in the calendar header view when allowed views have calendar work week view.", "type": "text" }, "allowedViewMonthLabel": "Month", - "@allowedViewMonthLabel": { + "@allowedViewMonthLabel": { "description": "Label that is displayed in the calendar header view when allowed views have calendar month view.", "type": "text" }, "allowedViewScheduleLabel": "Schedule", - "@allowedViewScheduleLabel": { + "@allowedViewScheduleLabel": { "description": "Label that is displayed in the calendar header view when allowed views have calendar schedule view.", "type": "text" }, "allowedViewTimelineDayLabel": "Timeline Day", - "@allowedViewTimelineDayLabel": { + "@allowedViewTimelineDayLabel": { "description": "Label that is displayed in the calendar header view when allowed views have calendar timeline day view.", "type": "text" }, "allowedViewTimelineWeekLabel": "Timeline Week", - "@allowedViewTimelineWeekLabel": { + "@allowedViewTimelineWeekLabel": { "description": "Label that is displayed in the calendar header view when allowed views have calendar timeline week view.", "type": "text" }, "allowedViewTimelineWorkWeekLabel": "Timeline Work Week", - "@allowedViewTimelineWorkWeekLabel": { + "@allowedViewTimelineWorkWeekLabel": { "description": "Label that is displayed in the calendar header view when allowed views have calendar timeline work view.", "type": "text" }, "allowedViewTimelineMonthLabel": "Timeline Month", - "@allowedViewTimelineMonthLabel": { + "@allowedViewTimelineMonthLabel": { "description": "Label that is displayed in the calendar header view when allowed views have calendar timeline month view.", "type": "text" }, "todayLabel": "Today", - "@todayLabel": { + "@todayLabel": { "description": "Label that is displayed in the calendar header view when calendar shows date picker on header interaction.", "type": "text" + }, + + "muharramLabel": "Muharram", + "@muharramLabel": { + "description": "The header string for the first month of hirji calendar.", + "type": "text" + }, + + "safarLabel": "Safar", + "@safarLabel": { + "description": "The header string for the second month of hirji calendar.", + "type": "text" + }, + + "rabi1Label": "Rabi' al-awwal", + "@rabi1Label": { + "description": "The header string for the third month of hirji calendar.", + "type": "text" + }, + + "rabi2Label": "Rabi' al-thani", + "@rabi2Label": { + "description": "The header string for the fourth month of hirji calendar.", + "type": "text" + }, + + "jumada1Label": "Jumada al-awwal", + "@jumada1Label": { + "description": "The header string for the fifth month of hirji calendar.", + "type": "text" + }, + + "jumada2Label": "Jumada al-thani", + "@jumada2Label": { + "description": "The header string for the sixth month of hirji calendar.", + "type": "text" + }, + + "rajabLabel": "Rajab", + "@rajabLabel": { + "description": "The header string for the seventh month of hirji calendar.", + "type": "text" + }, + + "shaabanLabel": "Sha'aban", + "@shaabanLabel": { + "description": "The header string for the eight month of hirji calendar.", + "type": "text" + }, + + "ramadanLabel": "Ramadan", + "@ramadanLabel": { + "description": "The header string for the ninth month of hirji calendar.", + "type": "text" + }, + + "shawwalLabel": "Shawwal", + "@shawwalLabel": { + "description": "The header string for the tenth month of hirji calendar.", + "type": "text" + }, + + "dhualqiLabel": "Dhu al-Qi'dah", + "@dhualqiLabel": { + "description": "The header string for the eleventh month of hirji calendar.", + "type": "text" + }, + + "dhualhiLabel": "Dhu al-Hijjah", + "@dhualhiLabel": { + "description": "The header string for the twelfth month of hirji calendar.", + "type": "text" + }, + + "shortMuharramLabel": "Muh.", + "@shortMuharramLabel": { + "description": "The header string for the first month of hirji calendar.", + "type": "text" + }, + + "shortSafarLabel": "Saf.", + "@shortSafarLabel": { + "description": "The header string for the second month of hirji calendar.", + "type": "text" + }, + + "shortRabi1Label": "Rabi. I", + "@shortRabi1Label": { + "description": "The header string for the third month of hirji calendar.", + "type": "text" + }, + + "shortRabi2Label": "Rabi. II", + "@shortRabi2Label": { + "description": "The header string for the fourth month of hirji calendar.", + "type": "text" + }, + + "shortJumada1Label": "Jum. I", + "@shortJumada1Label": { + "description": "The header string for the fifth month of hirji calendar.", + "type": "text" + }, + + "shortJumada2Label": "Jum. II", + "@shortJumada2Label": { + "description": "The header string for the sixth month of hirji calendar.", + "type": "text" + }, + + "shortRajabLabel": "Raj.", + "@shortRajabLabel": { + "description": "The header string for the seventh month of hirji calendar.", + "type": "text" + }, + + "shortShaabanLabel": "Sha.", + "@shortShaabanLabel": { + "description": "The header string for the eight month of hirji calendar.", + "type": "text" + }, + + "shortRamadanLabel": "Ram.", + "@shortRamadanLabel": { + "description": "The header string for the ninth month of hirji calendar.", + "type": "text" + }, + + "shortShawwalLabel": "Shaw.", + "@shortShawwalLabel": { + "description": "The header string for the tenth month of hirji calendar.", + "type": "text" + }, + + "shortDhualqiLabel": "Dhu'l-Q", + "@shortDhualqiLabel": { + "description": "The header string for the eleventh month of hirji calendar.", + "type": "text" + }, + + "shortDhualhiLabel": "Dhu'l-H", + "@shortDhualhiLabel": { + "description": "The header string for the twelfth month of hirji calendar.", + "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 a13449d93..9445e4397 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.json +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.json @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel": "Timeline Week", "allowedViewTimelineWorkWeekLabel": "Timeline Work Week", "allowedViewTimelineMonthLabel": "Timeline Month", - "todayLabel": "Today" + "todayLabel": "Today", + "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. I", + "shortRabi2Label": "Rabi. II", + "shortJumada1Label": "Jum. I", + "shortJumada2Label": "Jum. II", + "shortRajabLabel": "Raj.", + "shortShaabanLabel": "Sha.", + "shortRamadanLabel": "Ram.", + "shortShawwalLabel": "Shaw.", + "shortDhualqiLabel": "Dhu'l-Q", + "shortDhualhiLabel": "Dhu'l-H" } \ 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 af7d9f9ae..6c42e2940 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_es.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_es.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Semana de la cronología", "allowedViewTimelineWorkWeekLabel" : "Semana laboral de la línea de tiempo", "allowedViewTimelineMonthLabel" : "Mes de la cronología", -"todayLabel" : "Hoy" +"todayLabel" : "Hoy", +"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" : "Ramadán", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. yo", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. yo", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 7f17ab20b..2d9567c45 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_et.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_et.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Ajaskaala nädal", "allowedViewTimelineWorkWeekLabel" : "Ajaskaala töönädal", "allowedViewTimelineMonthLabel" : "Ajaskaala kuu", -"todayLabel" : "Täna" +"todayLabel" : "Täna", +"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" : "Ramadaan", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Mina", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Mina", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 03f29c798..0a06998ee 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_eu.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_eu.arb @@ -19,8 +19,32 @@ "allowedViewMonthLabel" : "Hilabetea", "allowedViewScheduleLabel" : "Ordutegia", "allowedViewTimelineDayLabel" : "Kronologia Eguna", -"allowedViewTimelineWeekLabel" : "Kronograma Astea", +"allowedViewTimelineWeekLabel" : "Kronologia Astea", "allowedViewTimelineWorkWeekLabel" : "Kronogramaren Lan Astea", "allowedViewTimelineMonthLabel" : "Kronograma Hilabetea", -"todayLabel" : "Gaur" +"todayLabel" : "Gaur", +"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" : "Ramadana", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Nik", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Nik", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 efcf6f2af..598c8c5ca 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fa.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fa.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "هفته جدول زمانی", "allowedViewTimelineWorkWeekLabel" : "هفته کار جدول زمانی", "allowedViewTimelineMonthLabel" : "ماه جدول زمانی", -"todayLabel" : "امروز" +"todayLabel" : "امروز", +"muharramLabel" : "محرم", +"safarLabel" : "صفر", +"rabi1Label" : "ربیع الاول", +"rabi2Label" : "ربیع الثانی", +"jumada1Label" : "جماد al الاول", +"jumada2Label" : "جمادا الثانی", +"rajabLabel" : "راجب", +"shaabanLabel" : "شعبان", +"ramadanLabel" : "ماه رمضان", +"shawwalLabel" : "شوال", +"dhualqiLabel" : "ذو القعده", +"dhualhiLabel" : "ذو الحجه", +"shortMuharramLabel" : "مه", +"shortSafarLabel" : "ساف", +"shortRabi1Label" : "ربیع من", +"shortRabi2Label" : "ربیع دوم", +"shortJumada1Label" : "جوم من", +"shortJumada2Label" : "جوم دوم", +"shortRajabLabel" : "راج", +"shortShaabanLabel" : "شا", +"shortRamadanLabel" : "رم.", +"shortShawwalLabel" : "شاو", +"shortDhualqiLabel" : "ذوالق", +"shortDhualhiLabel" : "ذوالحسن" } \ 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 1712f081b..c4b4fb6e6 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fi.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fi.arb @@ -1,7 +1,7 @@ { "noSelectedDateCalendarLabel" : "Ei valittua päivämäärää", "noEventsCalendarLabel" : "Ei tapahtumia", -"scheduleViewNewEventLabel" : "Ei mitään suunniteltua. Napauta luodaksesi.", +"scheduleViewNewEventLabel" : "Ei mitään suunniteltua. Napauta luoda.", "ofDataPagerLabel" : "/", "pagesDataPagerLabel" : "sivuja", "itemsDataPagerLabel" : "kohteita", @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Aikajanan viikko", "allowedViewTimelineWorkWeekLabel" : "Aikajanan työviikko", "allowedViewTimelineMonthLabel" : "Aikajanan kuukausi", -"todayLabel" : "Tänään" +"todayLabel" : "Tänään", +"muharramLabel" : "Muharram", +"safarLabel" : "Safari", +"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. Minä", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Minä", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 fe7a99380..31aedcc0e 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fil.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fil.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Linggo ng Timeline", "allowedViewTimelineWorkWeekLabel" : "Linggo ng Trabaho ng Timeline", "allowedViewTimelineMonthLabel" : "Buwan ng Timeline", -"todayLabel" : "Ngayon" +"todayLabel" : "Ngayon", +"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. Ako", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Ako", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Si Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 b1c0443ae..52226931d 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fr.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Semaine chronologique", "allowedViewTimelineWorkWeekLabel" : "Calendrier de la semaine de travail", "allowedViewTimelineMonthLabel" : "Mois de la chronologie", -"todayLabel" : "Aujourd'hui" +"todayLabel" : "Aujourd'hui", +"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. je", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. je", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 493d527ab..8fa388cb6 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gl.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Semana da cronoloxía", "allowedViewTimelineWorkWeekLabel" : "Semana de traballo da cronoloxía", "allowedViewTimelineMonthLabel" : "Mes do cronograma", -"todayLabel" : "Hoxe" +"todayLabel" : "Hoxe", +"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" : "Ramadán", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Eu", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Xum. Eu", +"shortJumada2Label" : "Xum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 1b7e4b59e..6ae7ad2f2 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gu.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gu.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "સમયરેખા સપ્તાહ", "allowedViewTimelineWorkWeekLabel" : "સમયરેખા વર્ક અઠવાડિયું", "allowedViewTimelineMonthLabel" : "સમયરેખા મહિનો", -"todayLabel" : "આજે" +"todayLabel" : "આજે", +"muharramLabel" : "મુહરમ", +"safarLabel" : "સફર", +"rabi1Label" : "રબી 'અલ-અવવાલ", +"rabi2Label" : "રબી 'અલ-થેની", +"jumada1Label" : "જુમાદા અલ-અવવાલ", +"jumada2Label" : "જુમાદા અલ-થiની", +"rajabLabel" : "રજબ", +"shaabanLabel" : "શઆબન", +"ramadanLabel" : "રમઝાન", +"shawwalLabel" : "શવાલ", +"dhualqiLabel" : "ધુ અલ-કિયાદહ", +"dhualhiLabel" : "ધુ અલ-હિજ્જા", +"shortMuharramLabel" : "મુહ.", +"shortSafarLabel" : "સેફ.", +"shortRabi1Label" : "રબી. હું", +"shortRabi2Label" : "રબી. II", +"shortJumada1Label" : "જામ. હું", +"shortJumada2Label" : "જામ. II", +"shortRajabLabel" : "રાજ.", +"shortShaabanLabel" : "શા.", +"shortRamadanLabel" : "રામ.", +"shortShawwalLabel" : "શો.", +"shortDhualqiLabel" : "ધૂલ-ક્યૂ", +"shortDhualhiLabel" : "ધૂલ-એચ" } \ 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 c2993f94a..8bc25189d 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_he.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_he.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "שבוע ציר הזמן", "allowedViewTimelineWorkWeekLabel" : "שבוע העבודה של ציר הזמן", "allowedViewTimelineMonthLabel" : "חודש ציר הזמן", -"todayLabel" : "היום" +"todayLabel" : "היום", +"muharramLabel" : "מוהרם", +"safarLabel" : "ספאר", +"rabi1Label" : "ראבי אל-עוואל", +"rabi2Label" : "ראבי אל-ת'אני", +"jumada1Label" : "ג'ומדה אל-עוואל", +"jumada2Label" : "ג'ומדה אל-ת'אני", +"rajabLabel" : "רג'אב", +"shaabanLabel" : "שעבאן", +"ramadanLabel" : "רמדאן", +"shawwalLabel" : "שאוווול", +"dhualqiLabel" : "דהו אל-קי'דה", +"dhualhiLabel" : "דהו אל-חיג'ה", +"shortMuharramLabel" : "מו", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "ראבי. אני", +"shortRabi2Label" : "ראבי. II", +"shortJumada1Label" : "ג'אם. אני", +"shortJumada2Label" : "ג'אם. II", +"shortRajabLabel" : "ראג '.", +"shortShaabanLabel" : "שא.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "שו.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 690b181ff..30e800d66 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hi.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hi.arb @@ -1,7 +1,7 @@ { "noSelectedDateCalendarLabel" : "कोई चयनित तिथि नहीं", "noEventsCalendarLabel" : "कोई आयोजन नहीं", -"scheduleViewNewEventLabel" : "कुछ भी योजनाबद्ध नहीं है। बनाने के लिए टैप करें।", +"scheduleViewNewEventLabel" : "कुछ भी प्लान नहीं किया। बनाने के लिए टैप करें।", "ofDataPagerLabel" : "का", "pagesDataPagerLabel" : "पृष्ठों", "itemsDataPagerLabel" : "आइटम", @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "समयरेखा सप्ताह", "allowedViewTimelineWorkWeekLabel" : "समयरेखा कार्य सप्ताह", "allowedViewTimelineMonthLabel" : "समय महीना", -"todayLabel" : "आज" +"todayLabel" : "आज", +"muharramLabel" : "मुहर्रम", +"safarLabel" : "सफ़र", +"rabi1Label" : "रबी 'अल-अव्वल", +"rabi2Label" : "रबी 'अल-थानी", +"jumada1Label" : "जुमादा अल-अव्वल", +"jumada2Label" : "जुमादा अल-थानी", +"rajabLabel" : "रज्जब", +"shaabanLabel" : "Sha'aban", +"ramadanLabel" : "रमजान", +"shawwalLabel" : "शावाल", +"dhualqiLabel" : "धू अल-कियूबाह", +"dhualhiLabel" : "धू अल-हिज्जाह", +"shortMuharramLabel" : "Muh।", +"shortSafarLabel" : "सैफ़।", +"shortRabi1Label" : "रबी। मैं", +"shortRabi2Label" : "रबी। द्वितीय", +"shortJumada1Label" : "Jum। मैं", +"shortJumada2Label" : "Jum। द्वितीय", +"shortRajabLabel" : "राज।", +"shortShaabanLabel" : "शा।", +"shortRamadanLabel" : "राम।", +"shortShawwalLabel" : "शॉ।", +"shortDhualqiLabel" : "Dhu'l-क्यू", +"shortDhualhiLabel" : "Dhu'l-एच" } \ 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 f8ae24541..700533ec5 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hr.arb @@ -6,7 +6,7 @@ "pagesDataPagerLabel" : "stranice", "itemsDataPagerLabel" : "predmeta", "pdfBookmarksLabel" : "Oznake", -"pdfNoBookmarksLabel" : "Nije pronađena nijedna oznaka", +"pdfNoBookmarksLabel" : "Nisu pronađene oznake", "pdfScrollStatusOfLabel" : "od", "pdfGoToPageLabel" : "Idi na stranicu", "pdfEnterPageNumberLabel" : "Unesite broj stranice", @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Tjedan vremenske trake", "allowedViewTimelineWorkWeekLabel" : "Tjedan radnog vremena", "allowedViewTimelineMonthLabel" : "Mjesec vremenske trake", -"todayLabel" : "Danas" +"todayLabel" : "Danas", +"muharramLabel" : "Muharram", +"safarLabel" : "Safar", +"rabi1Label" : "Rabi 'al-awwal", +"rabi2Label" : "Rabi 'al-thani", +"jumada1Label" : "Džumada al-avval", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "Rajab", +"shaabanLabel" : "Ša'aban", +"ramadanLabel" : "Ramazan", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hidždža", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Ja", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Ja", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Radna memorija.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 0bb88652d..ba9442a26 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hu.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hu.arb @@ -9,7 +9,7 @@ "pdfNoBookmarksLabel" : "Nem találhatók könyvjelzők", "pdfScrollStatusOfLabel" : "nak,-nek", "pdfGoToPageLabel" : "Menj az oldalra", -"pdfEnterPageNumberLabel" : "Írja be az oldalszámot", +"pdfEnterPageNumberLabel" : "Adja meg az oldalszámot", "pdfInvalidPageNumberLabel" : "Kérjük, adjon meg egy érvényes számot", "pdfPaginationDialogOkLabel" : "rendben", "pdfPaginationDialogCancelLabel" : "MEGSZÜNTETI", @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Idővonal hét", "allowedViewTimelineWorkWeekLabel" : "Időrend munkahete", "allowedViewTimelineMonthLabel" : "Idővonal Hónap", -"todayLabel" : "Ma" +"todayLabel" : "Ma", +"muharramLabel" : "Muharram", +"safarLabel" : "Szafar", +"rabi1Label" : "Rabi 'al-awwal", +"rabi2Label" : "Rabi 'al-thani", +"jumada1Label" : "Jumada al-awwal", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "Rajab", +"shaabanLabel" : "Sha'aban", +"ramadanLabel" : "Ramadán", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hidzsáh", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Szaf.", +"shortRabi1Label" : "Rabi. én", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. én", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 b8eb9e65f..a91ba8e91 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hy.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hy.arb @@ -18,9 +18,33 @@ "allowedViewWorkWeekLabel" : "Աշխատանքային շաբաթ", "allowedViewMonthLabel" : "Ամիս", "allowedViewScheduleLabel" : "Գրաֆիկ", -"allowedViewTimelineDayLabel" : "Elineամանակագրական օր", +"allowedViewTimelineDayLabel" : "Elineամանակացույցի օր", "allowedViewTimelineWeekLabel" : "Elineամանակացույցի շաբաթ", "allowedViewTimelineWorkWeekLabel" : "Elineամանակացույցի աշխատանքային շաբաթ", "allowedViewTimelineMonthLabel" : "Elineամանակացույցի ամիս", -"todayLabel" : "Այսօր" +"todayLabel" : "Այսօր", +"muharramLabel" : "Մուհարամ", +"safarLabel" : "Սաֆար", +"rabi1Label" : "Ռաբի ալ-Աուվալ", +"rabi2Label" : "Ռաբի ալ-թանի", +"jumada1Label" : "Umումադա ալ-վուալ", +"jumada2Label" : "Umումադա ալ-թանի", +"rajabLabel" : "Ռաջաբ", +"shaabanLabel" : "Շաաբան", +"ramadanLabel" : "Ռամադան", +"shawwalLabel" : "Շավալ", +"dhualqiLabel" : "Դհու ալ-Քիդա", +"dhualhiLabel" : "Huու ալ-Հիջա", +"shortMuharramLabel" : "Մուհ", +"shortSafarLabel" : "Սաֆ", +"shortRabi1Label" : "Ռաբի Ես", +"shortRabi2Label" : "Ռաբի II", +"shortJumada1Label" : "Umում Ես", +"shortJumada2Label" : "Umում II", +"shortRajabLabel" : "Ռաջ", +"shortShaabanLabel" : "Շա", +"shortRamadanLabel" : "Խոյ", +"shortShawwalLabel" : "Շոուն", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Դհուլ-Հ" } \ 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 28e58b887..eb7d31724 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_id.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_id.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Timeline Week", "allowedViewTimelineWorkWeekLabel" : "Timeline Work Week", "allowedViewTimelineMonthLabel" : "Bulan Garis Waktu", -"todayLabel" : "Hari ini" +"todayLabel" : "Hari ini", +"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" : "Syawal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. saya", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. saya", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dzul-Q", +"shortDhualhiLabel" : "Dzul-H" } \ 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 0b79e0500..c7bef680a 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_is.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_is.arb @@ -1,7 +1,7 @@ { "noSelectedDateCalendarLabel" : "Engin valin dagsetning", "noEventsCalendarLabel" : "Engir viðburðir", -"scheduleViewNewEventLabel" : "Ekkert skipulagt. Pikkaðu til að búa til.", +"scheduleViewNewEventLabel" : "Ekkert skipulagt. Pikkaðu á til að búa til.", "ofDataPagerLabel" : "af", "pagesDataPagerLabel" : "blaðsíður", "itemsDataPagerLabel" : "hlutir", @@ -20,7 +20,31 @@ "allowedViewScheduleLabel" : "Dagskrá", "allowedViewTimelineDayLabel" : "Tímalínudagur", "allowedViewTimelineWeekLabel" : "Tímalínuvika", -"allowedViewTimelineWorkWeekLabel" : "Viku tímalínu", +"allowedViewTimelineWorkWeekLabel" : "Vinnuvika tímalínu", "allowedViewTimelineMonthLabel" : "Tímalínumánuður", -"todayLabel" : "Í dag" +"todayLabel" : "Í dag", +"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. Ég", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Ég", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Vinnsluminni.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 0180a6414..7ee92af3d 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_it.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_it.arb @@ -19,8 +19,32 @@ "allowedViewMonthLabel" : "Mese", "allowedViewScheduleLabel" : "Programma", "allowedViewTimelineDayLabel" : "Timeline Day", -"allowedViewTimelineWeekLabel" : "Timeline Week", +"allowedViewTimelineWeekLabel" : "Settimana temporale", "allowedViewTimelineWorkWeekLabel" : "Timeline Work Week", "allowedViewTimelineMonthLabel" : "Mese della sequenza temporale", -"todayLabel" : "Oggi" +"todayLabel" : "Oggi", +"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. io", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. io", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 b8bfa6c11..5078a93ec 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ja.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ja.arb @@ -1,11 +1,11 @@ { -"noSelectedDateCalendarLabel" : "選択された日付なし", +"noSelectedDateCalendarLabel" : "日付が選択されていません", "noEventsCalendarLabel" : "イベントなし", -"scheduleViewNewEventLabel" : "予定はありません。タップして作成します。", +"scheduleViewNewEventLabel" : "何も計画されていません。タップして作成します。", "ofDataPagerLabel" : "の", "pagesDataPagerLabel" : "ページ", "itemsDataPagerLabel" : "アイテム", -"pdfBookmarksLabel" : "しおり", +"pdfBookmarksLabel" : "ブックマーク", "pdfNoBookmarksLabel" : "ブックマークが見つかりません", "pdfScrollStatusOfLabel" : "の", "pdfGoToPageLabel" : "ページに移動", @@ -18,9 +18,33 @@ "allowedViewWorkWeekLabel" : "労働週", "allowedViewMonthLabel" : "月", "allowedViewScheduleLabel" : "スケジュール", -"allowedViewTimelineDayLabel" : "タイムラインデー", +"allowedViewTimelineDayLabel" : "タイムラインの日", "allowedViewTimelineWeekLabel" : "タイムラインウィーク", -"allowedViewTimelineWorkWeekLabel" : "タイムライン作業週", +"allowedViewTimelineWorkWeekLabel" : "タイムラインワークウィーク", "allowedViewTimelineMonthLabel" : "タイムライン月", -"todayLabel" : "今日" +"todayLabel" : "今日", +"muharramLabel" : "ムハッラム", +"safarLabel" : "サファル", +"rabi1Label" : "Rabi'al-awwal", +"rabi2Label" : "ラビー・アルタニ", +"jumada1Label" : "ジュマーダアルアウワル", +"jumada2Label" : "ジュマーダッサーニ", +"rajabLabel" : "ラジャブ", +"shaabanLabel" : "シャアバーン", +"ramadanLabel" : "ラマダン", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "ズル・カイダ", +"dhualhiLabel" : "ズル・ヒッジャ", +"shortMuharramLabel" : "ムー。", +"shortSafarLabel" : "Saf。", +"shortRabi1Label" : "ラビ。私", +"shortRabi2Label" : "ラビ。 II", +"shortJumada1Label" : "ジャム。私", +"shortJumada2Label" : "ジャム。 II", +"shortRajabLabel" : "ラージ。", +"shortShaabanLabel" : "Sha。", +"shortRamadanLabel" : "羊。", +"shortShawwalLabel" : "ショー。", +"shortDhualqiLabel" : "ズル・カイダ", +"shortDhualhiLabel" : "ズル・カイダ" } \ 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 5e99baab3..197d2a919 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ka.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ka.arb @@ -1,7 +1,7 @@ { "noSelectedDateCalendarLabel" : "არჩეული თარიღი არ არის", -"noEventsCalendarLabel" : "არანაირი ღონისძიება", -"scheduleViewNewEventLabel" : "არაფერი დაგეგმილი. შეეხეთ შესაქმნელად.", +"noEventsCalendarLabel" : "არანაირი მოვლენა", +"scheduleViewNewEventLabel" : "არაფერია დაგეგმილი. შეეხეთ შესაქმნელად.", "ofDataPagerLabel" : "საქართველოს", "pagesDataPagerLabel" : "გვერდები", "itemsDataPagerLabel" : "საგნები", @@ -13,7 +13,7 @@ "pdfInvalidPageNumberLabel" : "გთხოვთ, შეიყვანოთ სწორი ნომერი", "pdfPaginationDialogOkLabel" : "კარგი", "pdfPaginationDialogCancelLabel" : "გაუქმება", -"allowedViewDayLabel" : "Დღეს", +"allowedViewDayLabel" : "Დღის", "allowedViewWeekLabel" : "კვირა", "allowedViewWorkWeekLabel" : "Სამუშაო კვირა", "allowedViewMonthLabel" : "თვე", @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "ქრონოლოგია კვირა", "allowedViewTimelineWorkWeekLabel" : "ქრონოლოგია სამუშაო კვირა", "allowedViewTimelineMonthLabel" : "ქრონოლოგია თვე", -"todayLabel" : "დღეს" +"todayLabel" : "დღეს", +"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" : "დულ-ჰ" } \ 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 611c92e47..bf64aa6e9 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kk.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kk.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Уақыт кестесі", "allowedViewTimelineWorkWeekLabel" : "Жұмыс кестесі", "allowedViewTimelineMonthLabel" : "Хронология айы", -"todayLabel" : "Бүгін" +"todayLabel" : "Бүгін", +"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" : "Зуль-Q", +"shortDhualhiLabel" : "Зуль-Х" } \ 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 e1c4a7d49..88f5de098 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_km.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_km.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "សប្តាហ៍កំណត់ពេល", "allowedViewTimelineWorkWeekLabel" : "សប្តាហ៍ការងារកំណត់ពេល", "allowedViewTimelineMonthLabel" : "ខែពេលវេលា", -"todayLabel" : "ថ្ងៃនេះ" +"todayLabel" : "ថ្ងៃនេះ", +"muharramLabel" : "Muharram", +"safarLabel" : "Safar", +"rabi1Label" : "រ៉ាប៊ី 'អាល់ - ដាស់", +"rabi2Label" : "រ៉ាប៊ីអាល់ - ថាវី", +"jumada1Label" : "ជូម៉ាដាអាល់អាល់វ៉ាល", +"jumada2Label" : "ចាមដាអាល់ - ថាវី", +"rajabLabel" : "រ៉ាបាប់", +"shaabanLabel" : "សាអាបាន", +"ramadanLabel" : "រ៉ាម៉ាដាន", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "ឌូអាល់ឈីឌា", +"dhualhiLabel" : "ឌូអាល់ហីចា", +"shortMuharramLabel" : "Muh ។", +"shortSafarLabel" : "សុវត្ថិភាព។", +"shortRabi1Label" : "រ៉ាប៊ី។ ខ្ញុំ", +"shortRabi2Label" : "រ៉ាប៊ី។ II", +"shortJumada1Label" : "ចាម។ ខ្ញុំ", +"shortJumada2Label" : "ចាម។ II", +"shortRajabLabel" : "រ៉ាក់។", +"shortShaabanLabel" : "សា។", +"shortRamadanLabel" : "អង្គ​ចងចាំ។", +"shortShawwalLabel" : "ទឹករលក។", +"shortDhualqiLabel" : "ឌូហល", +"shortDhualhiLabel" : "ឌូអេល - អេ" } \ 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 5f583ba79..186c0b9bf 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kn.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kn.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "ಟೈಮ್‌ಲೈನ್ ವಾರ", "allowedViewTimelineWorkWeekLabel" : "ಟೈಮ್‌ಲೈನ್ ಕೆಲಸದ ವಾರ", "allowedViewTimelineMonthLabel" : "ಟೈಮ್‌ಲೈನ್ ತಿಂಗಳು", -"todayLabel" : "ಇಂದು" +"todayLabel" : "ಇಂದು", +"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" : "ಧುಲ್-ಹೆಚ್" } \ 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 bb39fad12..d48392167 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ko.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ko.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "타임 라인 주", "allowedViewTimelineWorkWeekLabel" : "타임 라인 작업 주", "allowedViewTimelineMonthLabel" : "타임 라인 월", -"todayLabel" : "오늘" +"todayLabel" : "오늘", +"muharramLabel" : "무 하람", +"safarLabel" : "사 파르", +"rabi1Label" : "Rabi 'al-awwal", +"rabi2Label" : "Rabi 'al-thani", +"jumada1Label" : "Jumada al-awwal", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "라잡", +"shaabanLabel" : "샤아 반", +"ramadanLabel" : "라마단", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "두 알 히자", +"shortMuharramLabel" : "음.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "라비. 나는", +"shortRabi2Label" : "라비. II", +"shortJumada1Label" : "Jum. 나는", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "주권.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "램.", +"shortShawwalLabel" : "쇼.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 34eccca89..3e846dccb 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ky.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ky.arb @@ -21,6 +21,30 @@ "allowedViewTimelineDayLabel" : "Убакыт тилкеси күнү", "allowedViewTimelineWeekLabel" : "Timeline Week", "allowedViewTimelineWorkWeekLabel" : "Убакыт тилкесиндеги Жума", -"allowedViewTimelineMonthLabel" : "Убакыт тилкесиндеги ай", -"todayLabel" : "Бүгүн" +"allowedViewTimelineMonthLabel" : "Убакыт айы", +"todayLabel" : "Бүгүн", +"muharramLabel" : "Мухаррам", +"safarLabel" : "Сафар", +"rabi1Label" : "Раби 'ал-аввал", +"rabi2Label" : "Раби 'аль-тени", +"jumada1Label" : "Жумада ал-аввал", +"jumada2Label" : "Жумада ал-тени", +"rajabLabel" : "Ражаб", +"shaabanLabel" : "Шаабан", +"ramadanLabel" : "Рамазан", +"shawwalLabel" : "Шаввал", +"dhualqiLabel" : "Zhu al-Qi'dah", +"dhualhiLabel" : "Зуль-Хиджа", +"shortMuharramLabel" : "Мух.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Раби. I", +"shortRabi2Label" : "Раби. II", +"shortJumada1Label" : "Жум. I", +"shortJumada2Label" : "Жум. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Ша.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "Шоу.", +"shortDhualqiLabel" : "Zhu'l-Q", +"shortDhualhiLabel" : "Зуль-Х" } \ 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 5bff3ed41..226e048eb 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lo.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lo.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "ອາທິດ ກຳ ນົດເວລາ", "allowedViewTimelineWorkWeekLabel" : "ຕາຕະລາງເວລາເຮັດວຽກ", "allowedViewTimelineMonthLabel" : "ເດືອນ ກຳ ນົດເວລາ", -"todayLabel" : "ມື້​ນີ້" +"todayLabel" : "ມື້​ນີ້", +"muharramLabel" : "Muharram", +"safarLabel" : "Safar", +"rabi1Label" : "Rabi 'al-awwal", +"rabi2Label" : "Rabi 'al-thani", +"jumada1Label" : "Jumada al-awwal", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "ລາດ", +"shaabanLabel" : "ຊາ'aban", +"ramadanLabel" : "Ramadan", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "ປອດໄພ.", +"shortRabi1Label" : "ຣາເບ. ຂ້ອຍ", +"shortRabi2Label" : "ຣາເບ. II", +"shortJumada1Label" : "ຈູມ. ຂ້ອຍ", +"shortJumada2Label" : "ຈູມ. II", +"shortRajabLabel" : "ລາດ.", +"shortShaabanLabel" : "ຊາ.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "ໂກລ.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 2a17436e8..db85dd152 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lt.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lt.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Laiko juostos savaitė", "allowedViewTimelineWorkWeekLabel" : "Laiko juostos darbo savaitė", "allowedViewTimelineMonthLabel" : "Laiko juostos mėnuo", -"todayLabel" : "Šiandien" +"todayLabel" : "Šiandien", +"muharramLabel" : "Muharram", +"safarLabel" : "Safaras", +"rabi1Label" : "Rabi 'al-awwal", +"rabi2Label" : "Rabi 'al-thani", +"jumada1Label" : "Jumada al-awwal", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "Radžabas", +"shaabanLabel" : "Šaabanas", +"ramadanLabel" : "Ramadanas", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Aš", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Aš", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Radž.", +"shortShaabanLabel" : "Ša.", +"shortRamadanLabel" : "Avinas.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 e83861be8..afce0ec5b 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lv.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lv.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Laika skalas nedēļa", "allowedViewTimelineWorkWeekLabel" : "Laika skalas darba nedēļa", "allowedViewTimelineMonthLabel" : "Laika skala mēnesis", -"todayLabel" : "Šodien" +"todayLabel" : "Šodien", +"muharramLabel" : "Muharram", +"safarLabel" : "Safārs", +"rabi1Label" : "Rabi 'al-awwal", +"rabi2Label" : "Rabi 'al-thani", +"jumada1Label" : "Jumada al-awwal", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "Radžabs", +"shaabanLabel" : "Šaabans", +"ramadanLabel" : "Ramadāns", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Es", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Es", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Ša.", +"shortRamadanLabel" : "Auns.", +"shortShawwalLabel" : "Šovs.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 c65211453..cd49b80a4 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mk.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mk.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Недела на времеплов", "allowedViewTimelineWorkWeekLabel" : "Времепловна работна недела", "allowedViewTimelineMonthLabel" : "Месец на времеплов", -"todayLabel" : "Денес" +"todayLabel" : "Денес", +"muharramLabel" : "Мухарем", +"safarLabel" : "Сафар", +"rabi1Label" : "Раби ал-аввал", +"rabi2Label" : "Раби ал-тани", +"jumada1Label" : "Umумада ал-аввал", +"jumada2Label" : "Umумада ал-тани", +"rajabLabel" : "Раџаб", +"shaabanLabel" : "Шаабан", +"ramadanLabel" : "Рамазан", +"shawwalLabel" : "Шавал", +"dhualqiLabel" : "Huу ал-Кида", +"dhualhiLabel" : "Huу ал-хиџа", +"shortMuharramLabel" : "Мух", +"shortSafarLabel" : "Саф.", +"shortRabi1Label" : "Раби Јас", +"shortRabi2Label" : "Раби II", +"shortJumada1Label" : "Umум Јас", +"shortJumada2Label" : "Umум II", +"shortRajabLabel" : "Рај", +"shortShaabanLabel" : "Ша", +"shortRamadanLabel" : "Овен", +"shortShawwalLabel" : "Шоу", +"shortDhualqiLabel" : "Дул-П", +"shortDhualhiLabel" : "Дул-Х" } \ 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 16884ae59..b59a8ac4e 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ml.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ml.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "ടൈംലൈൻ ആഴ്ച", "allowedViewTimelineWorkWeekLabel" : "ടൈംലൈൻ വർക്ക് വീക്ക്", "allowedViewTimelineMonthLabel" : "ടൈംലൈൻ മാസം", -"todayLabel" : "ഇന്ന്" +"todayLabel" : "ഇന്ന്", +"muharramLabel" : "മുഹറം", +"safarLabel" : "സഫർ", +"rabi1Label" : "റാബി അൽ അവൽ", +"rabi2Label" : "റാബി അൽ താനി", +"jumada1Label" : "ജുമാദ അൽ അവ്വാൾ", +"jumada2Label" : "ജുമാദ അൽ താനി", +"rajabLabel" : "രാജാബ്", +"shaabanLabel" : "ഷാബാൻ", +"ramadanLabel" : "റമദാൻ", +"shawwalLabel" : "ഷാവാൽ", +"dhualqiLabel" : "ധു അൽ ക്വിദ", +"dhualhiLabel" : "ധു അൽ ഹിജ", +"shortMuharramLabel" : "മുഹ്.", +"shortSafarLabel" : "സേഫ്.", +"shortRabi1Label" : "റാബി. ഞാൻ", +"shortRabi2Label" : "റാബി. II", +"shortJumada1Label" : "ജം. ഞാൻ", +"shortJumada2Label" : "ജം. II", +"shortRajabLabel" : "രാജ്.", +"shortShaabanLabel" : "ഷാ.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "ഷാ.", +"shortDhualqiLabel" : "ദുൽ-ക്യു", +"shortDhualhiLabel" : "ദുൽ-എച്ച്" } \ 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 cfbff83c2..419587e68 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mn.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mn.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Он цагийн хэлхээс", "allowedViewTimelineWorkWeekLabel" : "Хугацааны ажлын долоо хоног", "allowedViewTimelineMonthLabel" : "Цаг хугацааны сар", -"todayLabel" : "Өнөөдөр" +"todayLabel" : "Өнөөдөр", +"muharramLabel" : "Мухаррам", +"safarLabel" : "Сафар", +"rabi1Label" : "Раби 'аль-аввал", +"rabi2Label" : "Раби 'аль-тени", +"jumada1Label" : "Жумада аль-аввал", +"jumada2Label" : "Жумада аль-тани", +"rajabLabel" : "Ражаб", +"shaabanLabel" : "Шаабан", +"ramadanLabel" : "Рамадан", +"shawwalLabel" : "Шаввал", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Зу аль-Хижжа", +"shortMuharramLabel" : "Мух.", +"shortSafarLabel" : "Аюулгүй.", +"shortRabi1Label" : "Раби. Би", +"shortRabi2Label" : "Раби. II", +"shortJumada1Label" : "Жум. Би", +"shortJumada2Label" : "Жум. II", +"shortRajabLabel" : "Раж.", +"shortShaabanLabel" : "Ша.", +"shortRamadanLabel" : "Рам.", +"shortShawwalLabel" : "Шоу.", +"shortDhualqiLabel" : "Зул-Q", +"shortDhualhiLabel" : "Зул-Х" } \ 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 4dd47ee60..60bd703a1 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mr.arb @@ -20,7 +20,31 @@ "allowedViewScheduleLabel" : "वेळापत्रक", "allowedViewTimelineDayLabel" : "टाइमलाइन दिवस", "allowedViewTimelineWeekLabel" : "टाइमलाइन आठवडा", -"allowedViewTimelineWorkWeekLabel" : "टाइमलाइन कार्य सप्ताह", +"allowedViewTimelineWorkWeekLabel" : "टाइमलाइन कार्य आठवडा", "allowedViewTimelineMonthLabel" : "टाइमलाइन महिना", -"todayLabel" : "आज" +"todayLabel" : "आज", +"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" : "धुळ-एच" } \ 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 e8462b863..23688e772 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ms.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ms.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Minggu Garis Masa", "allowedViewTimelineWorkWeekLabel" : "Minggu Kerja Garis Masa", "allowedViewTimelineMonthLabel" : "Garis Masa Bulan", -"todayLabel" : "Hari ini" +"todayLabel" : "Hari ini", +"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" : "Syawal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Saya", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Saya", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 8a16ee5b4..9b6edfaf2 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_my.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_my.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Timeline အပတ်", "allowedViewTimelineWorkWeekLabel" : "Timeline အလုပ်ရက်သတ္တပတ်", "allowedViewTimelineMonthLabel" : "အချိန်ဇယားလ", -"todayLabel" : "ဒီနေ့" +"todayLabel" : "ဒီနေ့", +"muharramLabel" : "Muharram", +"safarLabel" : "Safar", +"rabi1Label" : "Rabi 'al-awwal", +"rabi2Label" : "Rabi 'al-thani", +"jumada1Label" : "Jumada al-awwal", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "ရာဂျ", +"shaabanLabel" : "Sha'aban", +"ramadanLabel" : "Ramadan", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu Al-Hijjah", +"shortMuharramLabel" : "Muh ။", +"shortSafarLabel" : "Saf ။", +"shortRabi1Label" : "ရာဘီ။ ငါ", +"shortRabi2Label" : "ရာဘီ။ ၂", +"shortJumada1Label" : "Jum ။ ငါ", +"shortJumada2Label" : "Jum ။ ၂", +"shortRajabLabel" : "Raj ။", +"shortShaabanLabel" : "Sha ။", +"shortRamadanLabel" : "ရမ်။", +"shortShawwalLabel" : "Shaw ။", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H ကို" } \ 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 0283055bf..a3e180ff0 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nb.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nb.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Tidslinjeuke", "allowedViewTimelineWorkWeekLabel" : "Tidslinje arbeidsuke", "allowedViewTimelineMonthLabel" : "Tidslinjemåned", -"todayLabel" : "I dag" +"todayLabel" : "I dag", +"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" } \ 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 5bc7a43a5..2ca52e538 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ne.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ne.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "टाइमलाइन हप्ता", "allowedViewTimelineWorkWeekLabel" : "टाइमलाइन कार्य सप्ताह", "allowedViewTimelineMonthLabel" : "टाइमलाइन महीना", -"todayLabel" : "आज" +"todayLabel" : "आज", +"muharramLabel" : "मुहर्रम", +"safarLabel" : "सफार", +"rabi1Label" : "रबी अल अलवाल", +"rabi2Label" : "रबी 'अल-थानी", +"jumada1Label" : "जुमादा अल-अवाल", +"jumada2Label" : "जुमादा अल-थानी", +"rajabLabel" : "रजब", +"shaabanLabel" : "Sha'aban", +"ramadanLabel" : "रमजान", +"shawwalLabel" : "शोवल", +"dhualqiLabel" : "धु अल Qi'dah", +"dhualhiLabel" : "धु अल-हिज्जा", +"shortMuharramLabel" : "मुह।", +"shortSafarLabel" : "सफ।", +"shortRabi1Label" : "रबी। I", +"shortRabi2Label" : "रबी। II", +"shortJumada1Label" : "Jum। I", +"shortJumada2Label" : "Jum। II", +"shortRajabLabel" : "राज।", +"shortShaabanLabel" : "Sha।", +"shortRamadanLabel" : "राम।", +"shortShawwalLabel" : "श", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 2f50885dd..dcf2a1833 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nl.arb @@ -12,7 +12,7 @@ "pdfEnterPageNumberLabel" : "Voer het paginanummer in", "pdfInvalidPageNumberLabel" : "Gelieve een geldig nummer invoeren", "pdfPaginationDialogOkLabel" : "OK", -"pdfPaginationDialogCancelLabel" : "ANNULEER", +"pdfPaginationDialogCancelLabel" : "ANNULEREN", "allowedViewDayLabel" : "Dag", "allowedViewWeekLabel" : "Week", "allowedViewWorkWeekLabel" : "Werkweek", @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Tijdlijn Week", "allowedViewTimelineWorkWeekLabel" : "Tijdlijn werkweek", "allowedViewTimelineMonthLabel" : "Tijdlijn maand", -"todayLabel" : "Vandaag" +"todayLabel" : "Vandaag", +"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. ik", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. ik", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 11e005ffc..1cc04afcc 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pa.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pa.arb @@ -10,7 +10,7 @@ "pdfScrollStatusOfLabel" : "ਦੇ", "pdfGoToPageLabel" : "ਪੇਜ ਤੇ ਜਾਓ", "pdfEnterPageNumberLabel" : "ਪੇਜ ਨੰਬਰ ਦਰਜ ਕਰੋ", -"pdfInvalidPageNumberLabel" : "ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਸਹੀ ਨੰਬਰ ਦਾਖਲ ਕਰੋ", +"pdfInvalidPageNumberLabel" : "ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਯੋਗ ਨੰਬਰ ਦਾਖਲ ਕਰੋ", "pdfPaginationDialogOkLabel" : "ਠੀਕ ਹੈ", "pdfPaginationDialogCancelLabel" : "ਰੱਦ", "allowedViewDayLabel" : "ਦਿਨ", @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "ਟਾਈਮਲਾਈਨ ਹਫ਼ਤਾ", "allowedViewTimelineWorkWeekLabel" : "ਟਾਈਮਲਾਈਨ ਵਰਕ ਵੀਕ", "allowedViewTimelineMonthLabel" : "ਟਾਈਮਲਾਈਨ ਮਹੀਨਾ", -"todayLabel" : "ਅੱਜ" +"todayLabel" : "ਅੱਜ", +"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" : "ਧੂਅਲ-ਕਿ Q", +"shortDhualhiLabel" : "ਧੂਲ-ਐਚ" } \ 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 ce1ca9a79..cfd42b65d 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pl.arb @@ -11,16 +11,40 @@ "pdfGoToPageLabel" : "Idź do strony", "pdfEnterPageNumberLabel" : "Wprowadź numer strony", "pdfInvalidPageNumberLabel" : "Proszę wprowadzić poprawny numer", -"pdfPaginationDialogOkLabel" : "dobrze", +"pdfPaginationDialogOkLabel" : "ok", "pdfPaginationDialogCancelLabel" : "ANULUJ", "allowedViewDayLabel" : "Dzień", "allowedViewWeekLabel" : "Tydzień", "allowedViewWorkWeekLabel" : "Tydzień pracy", "allowedViewMonthLabel" : "Miesiąc", "allowedViewScheduleLabel" : "Harmonogram", -"allowedViewTimelineDayLabel" : "Dzień osi czasu", +"allowedViewTimelineDayLabel" : "Dzień na osi czasu", "allowedViewTimelineWeekLabel" : "Tydzień osi czasu", "allowedViewTimelineWorkWeekLabel" : "Tydzień pracy na osi czasu", "allowedViewTimelineMonthLabel" : "Miesiąc osi czasu", -"todayLabel" : "Dzisiaj" +"todayLabel" : "Dzisiaj", +"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. ja", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. ja", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Baran.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 0eefc72b4..699efb6ac 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ps.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ps.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "د مهال ویش", "allowedViewTimelineWorkWeekLabel" : "د مهال ویش کاري اونۍ", "allowedViewTimelineMonthLabel" : "د مهال ویش میاشت", -"todayLabel" : "نن" +"todayLabel" : "نن", +"muharramLabel" : "محرم", +"safarLabel" : "صفر", +"rabi1Label" : "ربیع الاول", +"rabi2Label" : "ربیع الثاني", +"jumada1Label" : "جمعه الاول", +"jumada2Label" : "جمعه الثاني", +"rajabLabel" : "رجب", +"shaabanLabel" : "شعبان", +"ramadanLabel" : "رمضان", +"shawwalLabel" : "شوال", +"dhualqiLabel" : "ذي القده", +"dhualhiLabel" : "ذو الحجjah", +"shortMuharramLabel" : "م.", +"shortSafarLabel" : "صف.", +"shortRabi1Label" : "ربي. زه", +"shortRabi2Label" : "ربي. II", +"shortJumada1Label" : "جم. زه", +"shortJumada2Label" : "جم. II", +"shortRajabLabel" : "راج.", +"shortShaabanLabel" : "شا.", +"shortRamadanLabel" : "رام.", +"shortShawwalLabel" : "شا.", +"shortDhualqiLabel" : "ذوالق", +"shortDhualhiLabel" : "ذوالح" } \ 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 c31b75373..59d5d7546 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Semana do cronograma", "allowedViewTimelineWorkWeekLabel" : "Cronograma da semana de trabalho", "allowedViewTimelineMonthLabel" : "Mês da linha do tempo", -"todayLabel" : "Hoje" +"todayLabel" : "Hoje", +"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" : "Ramadã", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Eu", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Eu", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 709cde3fd..1b385cab3 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt_PT.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt_PT.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Semana do cronograma", "allowedViewTimelineWorkWeekLabel" : "Cronograma da semana de trabalho", "allowedViewTimelineMonthLabel" : "Mês da linha do tempo", -"todayLabel" : "Hoje" +"todayLabel" : "Hoje", +"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" : "Ramadã", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Eu", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Eu", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 8c9be05c2..13729f3a5 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ro.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ro.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Timeline Week", "allowedViewTimelineWorkWeekLabel" : "Cronologia săptămânii de lucru", "allowedViewTimelineMonthLabel" : "Luna cronologică", -"todayLabel" : "Azi" +"todayLabel" : "Astăzi", +"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" : "Ramadanul", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Eu", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Eu", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Berbec.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 41ab3ba53..bc568c937 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ru.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ru.arb @@ -11,7 +11,7 @@ "pdfGoToPageLabel" : "Перейти на страницу", "pdfEnterPageNumberLabel" : "Введите номер страницы", "pdfInvalidPageNumberLabel" : "пожалуйста введите правильное число", -"pdfPaginationDialogOkLabel" : "хорошо", +"pdfPaginationDialogOkLabel" : "ОК", "pdfPaginationDialogCancelLabel" : "ОТМЕНА", "allowedViewDayLabel" : "День", "allowedViewWeekLabel" : "Неделя", @@ -20,7 +20,31 @@ "allowedViewScheduleLabel" : "График", "allowedViewTimelineDayLabel" : "Хронология День", "allowedViewTimelineWeekLabel" : "Хронология недели", -"allowedViewTimelineWorkWeekLabel" : "График работы за неделю", +"allowedViewTimelineWorkWeekLabel" : "График работы неделя", "allowedViewTimelineMonthLabel" : "Хронология Месяц", -"todayLabel" : "Cегодня" +"todayLabel" : "Cегодня", +"muharramLabel" : "Мухаррам", +"safarLabel" : "Сафар", +"rabi1Label" : "Раби аль-Аввал", +"rabi2Label" : "Раби аль-тани", +"jumada1Label" : "Джумада аль-Аввал", +"jumada2Label" : "Джумада аль-тани", +"rajabLabel" : "Раджаб", +"shaabanLabel" : "Шаабан", +"ramadanLabel" : "Рамадан", +"shawwalLabel" : "Шавваль", +"dhualqiLabel" : "Зу аль-Киада", +"dhualhiLabel" : "Зу аль-Хиджа", +"shortMuharramLabel" : "Мух.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Лави. я", +"shortRabi2Label" : "Лави. II", +"shortJumada1Label" : "Джум. я", +"shortJumada2Label" : "Джум. II", +"shortRajabLabel" : "Радж.", +"shortShaabanLabel" : "Ша.", +"shortRamadanLabel" : "ОЗУ.", +"shortShawwalLabel" : "Шоу.", +"shortDhualqiLabel" : "Зуль-К", +"shortDhualhiLabel" : "Зуль-Х" } \ 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 5b271129c..53e5e9f25 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_si.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_si.arb @@ -13,7 +13,7 @@ "pdfInvalidPageNumberLabel" : "කරුණාකර වලංගු අංකයක් ඇතුළත් කරන්න", "pdfPaginationDialogOkLabel" : "හරි", "pdfPaginationDialogCancelLabel" : "අවලංගු කරන්න", -"allowedViewDayLabel" : "දවස", +"allowedViewDayLabel" : "දිනය", "allowedViewWeekLabel" : "සතිය", "allowedViewWorkWeekLabel" : "වැඩ සතිය", "allowedViewMonthLabel" : "මාසික", @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "කාල නියමය", "allowedViewTimelineWorkWeekLabel" : "කාල නියමය වැඩ සතිය", "allowedViewTimelineMonthLabel" : "කාල නියමය", -"todayLabel" : "අද" +"todayLabel" : "අද", +"muharramLabel" : "මුහාරම්", +"safarLabel" : "සෆාර්", +"rabi1Label" : "රබී අල් අව්වාල්", +"rabi2Label" : "රබී අල් තානි", +"jumada1Label" : "ජුමාඩා අල් අව්වාල්", +"jumada2Label" : "ජුමාඩා අල් තානි", +"rajabLabel" : "රාජාබ්", +"shaabanLabel" : "ෂාබාන්", +"ramadanLabel" : "රාමදාන්", +"shawwalLabel" : "ෂව්වාල්", +"dhualqiLabel" : "ධු අල්-කයිඩා", +"dhualhiLabel" : "ධු අල් හිජ්ජා", +"shortMuharramLabel" : "මුහ්.", +"shortSafarLabel" : "සේෆ්.", +"shortRabi1Label" : "රබී. මම", +"shortRabi2Label" : "රබී. II", +"shortJumada1Label" : "ජුම්. මම", +"shortJumada2Label" : "ජුම්. II", +"shortRajabLabel" : "රාජ්.", +"shortShaabanLabel" : "ෂා.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "ෂෝ.", +"shortDhualqiLabel" : "දුල්-කියු", +"shortDhualhiLabel" : "දුල්-එච්" } \ 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 7d091f818..88f9043d0 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sk.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sk.arb @@ -10,7 +10,7 @@ "pdfScrollStatusOfLabel" : "z", "pdfGoToPageLabel" : "Chod na stranu", "pdfEnterPageNumberLabel" : "Zadajte číslo stránky", -"pdfInvalidPageNumberLabel" : "Prosím vložte platné číslo", +"pdfInvalidPageNumberLabel" : "Prosím zadajte platné číslo", "pdfPaginationDialogOkLabel" : "Ok", "pdfPaginationDialogCancelLabel" : "ZRUŠIŤ", "allowedViewDayLabel" : "Deň", @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Týždeň časovej osi", "allowedViewTimelineWorkWeekLabel" : "Pracovný týždeň na časovej osi", "allowedViewTimelineMonthLabel" : "Časová os Mesiac", -"todayLabel" : "Dnes" +"todayLabel" : "Dnes", +"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" : "Ramadán", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Ja", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Ja", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 6cb1ccb83..b7287fbf5 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sl.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Teden časovne osi", "allowedViewTimelineWorkWeekLabel" : "Časovni delovni teden", "allowedViewTimelineMonthLabel" : "Mesec časovne osi", -"todayLabel" : "Danes" +"todayLabel" : "Danes", +"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" : "Ramazan", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. jaz", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. jaz", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Oven.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 116bf6f2e..aec677c6b 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sq.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sq.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Java e kronologjisë", "allowedViewTimelineWorkWeekLabel" : "Java e punës me kronologjinë", "allowedViewTimelineMonthLabel" : "Muaji i kronologjisë", -"todayLabel" : "Sot" +"todayLabel" : "Sot", +"muharramLabel" : "Muharrem", +"safarLabel" : "Safar", +"rabi1Label" : "Rabi 'al-awwal", +"rabi2Label" : "Rabi 'al-thani", +"jumada1Label" : "Xhumada el-auval", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "Rajab", +"shaabanLabel" : "Sha'aban", +"ramadanLabel" : "Ramazani", +"shawwalLabel" : "Shaval", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hixhah", +"shortMuharramLabel" : "Muh", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Une", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum Une", +"shortJumada2Label" : "Jum II", +"shortRajabLabel" : "Raj", +"shortShaabanLabel" : "Sha", +"shortRamadanLabel" : "Ram", +"shortShawwalLabel" : "Shaw", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhul-H" } \ 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 389c11573..6d9c6cad6 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sr.arb @@ -11,16 +11,40 @@ "pdfGoToPageLabel" : "Иди на страну", "pdfEnterPageNumberLabel" : "Унесите број странице", "pdfInvalidPageNumberLabel" : "Молимо Вас да унесете важећи број", -"pdfPaginationDialogOkLabel" : "ок", +"pdfPaginationDialogOkLabel" : "У реду", "pdfPaginationDialogCancelLabel" : "ПОНИШТИТИ, ОТКАЗАТИ", "allowedViewDayLabel" : "Дан", "allowedViewWeekLabel" : "Недеља", "allowedViewWorkWeekLabel" : "Радна недеља", "allowedViewMonthLabel" : "Месец дана", "allowedViewScheduleLabel" : "Распоред", -"allowedViewTimelineDayLabel" : "Дан хронологије", +"allowedViewTimelineDayLabel" : "Дан временске оријентације", "allowedViewTimelineWeekLabel" : "Недеља временске оријентације", "allowedViewTimelineWorkWeekLabel" : "Радна недеља временског следа", "allowedViewTimelineMonthLabel" : "Месец хронолошког следа", -"todayLabel" : "Данас" +"todayLabel" : "Данас", +"muharramLabel" : "Мухаррам", +"safarLabel" : "Сафар", +"rabi1Label" : "Раби 'ал-аввал", +"rabi2Label" : "Раби 'ал-тхани", +"jumada1Label" : "Јумада ал-аввал", +"jumada2Label" : "Јумада ал-тхани", +"rajabLabel" : "Рајаб", +"shaabanLabel" : "Сха'абан", +"ramadanLabel" : "Рамазан", +"shawwalLabel" : "Схаввал", +"dhualqiLabel" : "Дху ал-Ки'дах", +"dhualhiLabel" : "Дху ал-Хиџа", +"shortMuharramLabel" : "Мух.", +"shortSafarLabel" : "Саф.", +"shortRabi1Label" : "Раби. Ја", +"shortRabi2Label" : "Раби. ИИ", +"shortJumada1Label" : "Јум. Ја", +"shortJumada2Label" : "Јум. ИИ", +"shortRajabLabel" : "Рај.", +"shortShaabanLabel" : "Сха.", +"shortRamadanLabel" : "РАМ.", +"shortShawwalLabel" : "Схав.", +"shortDhualqiLabel" : "Дху'л-К", +"shortDhualhiLabel" : "Дху'л-Х" } \ 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 03f774498..7e9037181 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sv.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sv.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Tidslinje vecka", "allowedViewTimelineWorkWeekLabel" : "Tidslinje arbetsvecka", "allowedViewTimelineMonthLabel" : "Tidslinje månad", -"todayLabel" : "I dag" +"todayLabel" : "I dag", +"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. Jag", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Jag", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Bagge.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 8bbe03d02..4c6a85ce9 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sw.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sw.arb @@ -12,7 +12,7 @@ "pdfEnterPageNumberLabel" : "Ingiza nambari ya ukurasa", "pdfInvalidPageNumberLabel" : "Tafadhali ingiza nambari halali", "pdfPaginationDialogOkLabel" : "sawa", -"pdfPaginationDialogCancelLabel" : "GHAFU", +"pdfPaginationDialogCancelLabel" : "FUTA", "allowedViewDayLabel" : "Siku", "allowedViewWeekLabel" : "Wiki", "allowedViewWorkWeekLabel" : "Wiki ya Kazi", @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Wiki ya ratiba", "allowedViewTimelineWorkWeekLabel" : "Wiki ya Kazi ya ratiba", "allowedViewTimelineMonthLabel" : "Mwezi wa Ratiba", -"todayLabel" : "Leo" +"todayLabel" : "Leo", +"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" : "Ramadhani", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Mimi", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Mimi", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 320f0ac8d..4a68a001f 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ta.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ta.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "காலவரிசை வாரம்", "allowedViewTimelineWorkWeekLabel" : "காலவரிசை வேலை வாரம்", "allowedViewTimelineMonthLabel" : "காலக்கெடு மாதம்", -"todayLabel" : "இன்று" +"todayLabel" : "இன்று", +"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" : "துல்-எச்" } \ 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 0c9657b80..38edd947c 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_te.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_te.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "కాలక్రమం వారం", "allowedViewTimelineWorkWeekLabel" : "కాలక్రమం పని వారం", "allowedViewTimelineMonthLabel" : "కాలక్రమం నెల", -"todayLabel" : "ఈ రోజు" +"todayLabel" : "ఈ రోజు", +"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" : "ధుల్-హెచ్" } \ 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 2a28573bf..c77c55c09 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_th.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_th.arb @@ -1,7 +1,7 @@ { "noSelectedDateCalendarLabel" : "ไม่มีวันที่เลือก", "noEventsCalendarLabel" : "ไม่มีเหตุการณ์", -"scheduleViewNewEventLabel" : "ไม่มีอะไรวางแผน แตะเพื่อสร้าง", +"scheduleViewNewEventLabel" : "ไม่มีอะไรที่วางแผนไว้ แตะเพื่อสร้าง", "ofDataPagerLabel" : "ของ", "pagesDataPagerLabel" : "หน้า", "itemsDataPagerLabel" : "รายการ", @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "ไทม์ไลน์สัปดาห์", "allowedViewTimelineWorkWeekLabel" : "Timeline Work Week", "allowedViewTimelineMonthLabel" : "ไทม์ไลน์เดือน", -"todayLabel" : "วันนี้" +"todayLabel" : "วันนี้", +"muharramLabel" : "มูฮาร์ราม", +"safarLabel" : "Safar", +"rabi1Label" : "Rabi 'al-awwal", +"rabi2Label" : "Rabi 'al-thani", +"jumada1Label" : "Jumada al-awwal", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "จ", +"shaabanLabel" : "Sha'aban", +"ramadanLabel" : "เดือนรอมฎอน", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "ปลอดภัย.", +"shortRabi1Label" : "ราบี. ผม", +"shortRabi2Label" : "ราบี. II", +"shortJumada1Label" : "จุ๋ม. ผม", +"shortJumada2Label" : "จุ๋ม. II", +"shortRajabLabel" : "ราช.", +"shortShaabanLabel" : "ชา.", +"shortRamadanLabel" : "แกะ.", +"shortShawwalLabel" : "ชอว์.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 fe7a99380..31aedcc0e 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tl.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Linggo ng Timeline", "allowedViewTimelineWorkWeekLabel" : "Linggo ng Trabaho ng Timeline", "allowedViewTimelineMonthLabel" : "Buwan ng Timeline", -"todayLabel" : "Ngayon" +"todayLabel" : "Ngayon", +"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. Ako", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Ako", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Si Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 e8870ad0f..d718d4f46 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tr.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Zaman Çizelgesi Haftası", "allowedViewTimelineWorkWeekLabel" : "Zaman Çizelgesi Çalışma Haftası", "allowedViewTimelineMonthLabel" : "Zaman Çizelgesi Ayı", -"todayLabel" : "Bugün" +"todayLabel" : "Bugün", +"muharramLabel" : "Muharrem", +"safarLabel" : "Safar", +"rabi1Label" : "Rebiülevvel", +"rabi2Label" : "Rabi 'al-thani", +"jumada1Label" : "Jumada al-evvel", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "Receb", +"shaabanLabel" : "Sha'aban", +"ramadanLabel" : "Ramazan", +"shawwalLabel" : "Şevval", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Zilhicce", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. ben", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. ben", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Veri deposu.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Zil-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 f75589c8c..f4a24e866 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uk.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uk.arb @@ -11,7 +11,7 @@ "pdfGoToPageLabel" : "Перейти на сторінку", "pdfEnterPageNumberLabel" : "Введіть номер сторінки", "pdfInvalidPageNumberLabel" : "Введіть дійсний номер", -"pdfPaginationDialogOkLabel" : "гаразд", +"pdfPaginationDialogOkLabel" : "в порядку", "pdfPaginationDialogCancelLabel" : "СКАСУВАТИ", "allowedViewDayLabel" : "День", "allowedViewWeekLabel" : "Тиждень", @@ -19,8 +19,32 @@ "allowedViewMonthLabel" : "Місяць", "allowedViewScheduleLabel" : "Розклад", "allowedViewTimelineDayLabel" : "День часової шкали", -"allowedViewTimelineWeekLabel" : "Тиждень часових шкал", +"allowedViewTimelineWeekLabel" : "Тиждень шкали часу", "allowedViewTimelineWorkWeekLabel" : "Хронологія робочого тижня", "allowedViewTimelineMonthLabel" : "Місяць часової шкали", -"todayLabel" : "Сьогодні" +"todayLabel" : "Сьогодні", +"muharramLabel" : "Мухаррам", +"safarLabel" : "Сафар", +"rabi1Label" : "Рабі аль-Ауваль", +"rabi2Label" : "Рабі аль-Тані", +"jumada1Label" : "Джумада аль-авваль", +"jumada2Label" : "Джумада аль-Тані", +"rajabLabel" : "Раджаб", +"shaabanLabel" : "Шаабан", +"ramadanLabel" : "Рамадан", +"shawwalLabel" : "Шавваль", +"dhualqiLabel" : "Дху аль-Кіда", +"dhualhiLabel" : "Дху аль-Хіджа", +"shortMuharramLabel" : "Мм", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Рабі. Я", +"shortRabi2Label" : "Рабі. II", +"shortJumada1Label" : "Jum. Я", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Радж.", +"shortShaabanLabel" : "Ша.", +"shortRamadanLabel" : "ОЗП.", +"shortShawwalLabel" : "Шоу.", +"shortDhualqiLabel" : "Зул-Q", +"shortDhualhiLabel" : "Зул-Н" } \ 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 59f48d4e4..546b34e68 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ur.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ur.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "ٹائم لائن ہفتہ", "allowedViewTimelineWorkWeekLabel" : "ٹائم لائن ورک ویک", "allowedViewTimelineMonthLabel" : "ٹائم لائن مہینہ", -"todayLabel" : "آج" +"todayLabel" : "آج", +"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" : "ذوالحل" } \ 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 b406f4294..b42d10860 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uz.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uz.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Vaqt chizig'i haftasi", "allowedViewTimelineWorkWeekLabel" : "Vaqt jadvalidagi ish haftasi", "allowedViewTimelineMonthLabel" : "Vaqt chizig'i oyi", -"todayLabel" : "Bugun" +"todayLabel" : "Bugun", +"muharramLabel" : "Muharram", +"safarLabel" : "Safar", +"rabi1Label" : "Rabi 'al-avval", +"rabi2Label" : "Rabi 'al-thani", +"jumada1Label" : "Jumada al-avval", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "Rajab", +"shaabanLabel" : "Sha'bon", +"ramadanLabel" : "Ramazon", +"shawwalLabel" : "Shavvol", +"dhualqiLabel" : "Zul al-Qida", +"dhualhiLabel" : "Zul al-Hijja", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Xavfsiz", +"shortRabi1Label" : "Rabi. Men", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Men", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shou.", +"shortDhualqiLabel" : "Zul-Q", +"shortDhualhiLabel" : "Zulh" } \ 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 907e38e5d..df9ac8bbf 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_vi.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_vi.arb @@ -8,7 +8,7 @@ "pdfBookmarksLabel" : "Dấu trang", "pdfNoBookmarksLabel" : "Không tìm thấy dấu trang", "pdfScrollStatusOfLabel" : "của", -"pdfGoToPageLabel" : "Đi đến trang", +"pdfGoToPageLabel" : "Đi tới trang", "pdfEnterPageNumberLabel" : "Nhập số trang", "pdfInvalidPageNumberLabel" : "Vui lòng nhập một số hợp lệ", "pdfPaginationDialogOkLabel" : "đồng ý", @@ -21,6 +21,30 @@ "allowedViewTimelineDayLabel" : "Ngày dòng thời gian", "allowedViewTimelineWeekLabel" : "Dòng thời gian tuần", "allowedViewTimelineWorkWeekLabel" : "Thời gian làm việc tuần", -"allowedViewTimelineMonthLabel" : "Dòng thời gian Tháng", -"todayLabel" : "Hôm nay" +"allowedViewTimelineMonthLabel" : "Thời gian tháng", +"todayLabel" : "Hôm nay", +"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" : "Ờ.", +"shortSafarLabel" : "Két sắt.", +"shortRabi1Label" : "Rabi. Tôi", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Tôi", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H" } \ 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 903734a2b..aae0e1bc5 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "时间轴周", "allowedViewTimelineWorkWeekLabel" : "时间轴工作周", "allowedViewTimelineMonthLabel" : "时间轴月", -"todayLabel" : "今天" +"todayLabel" : "今天", +"muharramLabel" : "穆哈拉姆", +"safarLabel" : "萨法尔", +"rabi1Label" : "拉比·阿瓦尔", +"rabi2Label" : "拉比阿尔塔尼", +"jumada1Label" : "朱马达·阿瓦尔", +"jumada2Label" : "朱马达·萨塔尼(Jumada al-thani)", +"rajabLabel" : "拉贾卜", +"shaabanLabel" : "沙阿班", +"ramadanLabel" : "斋月", +"shawwalLabel" : "肖瓦尔", +"dhualqiLabel" : "齐达", +"dhualhiLabel" : "杜·希贾", +"shortMuharramLabel" : "嗯", +"shortSafarLabel" : "Saf。", +"shortRabi1Label" : "拉比一世", +"shortRabi2Label" : "拉比II", +"shortJumada1Label" : "um一世", +"shortJumada2Label" : "um II", +"shortRajabLabel" : "拉吉", +"shortShaabanLabel" : "沙。", +"shortRamadanLabel" : "内存。", +"shortShawwalLabel" : "肖", +"shortDhualqiLabel" : "杜克", +"shortDhualhiLabel" : "杜赫" } \ 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 5d0bf2231..032c31db5 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_HK.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_HK.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "時間軸週", "allowedViewTimelineWorkWeekLabel" : "時間軸工作週", "allowedViewTimelineMonthLabel" : "時間軸月", -"todayLabel" : "今天" +"todayLabel" : "今天", +"muharramLabel" : "穆哈拉姆", +"safarLabel" : "薩法爾", +"rabi1Label" : "拉比·阿瓦爾", +"rabi2Label" : "拉比阿爾塔尼", +"jumada1Label" : "朱馬達·阿瓦爾", +"jumada2Label" : "朱馬達·薩塔尼(Jumada al-thani)", +"rajabLabel" : "拉賈卜", +"shaabanLabel" : "沙阿班", +"ramadanLabel" : "齋月", +"shawwalLabel" : "肖瓦爾", +"dhualqiLabel" : "齊達", +"dhualhiLabel" : "杜·希賈", +"shortMuharramLabel" : "嗯", +"shortSafarLabel" : "Saf。", +"shortRabi1Label" : "拉比一世", +"shortRabi2Label" : "拉比II", +"shortJumada1Label" : "um一世", +"shortJumada2Label" : "um II", +"shortRajabLabel" : "拉吉", +"shortShaabanLabel" : "沙。", +"shortRamadanLabel" : "內存。", +"shortShawwalLabel" : "肖", +"shortDhualqiLabel" : "杜克", +"shortDhualhiLabel" : "杜赫" } \ 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 5d0bf2231..032c31db5 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_TW.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_TW.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "時間軸週", "allowedViewTimelineWorkWeekLabel" : "時間軸工作週", "allowedViewTimelineMonthLabel" : "時間軸月", -"todayLabel" : "今天" +"todayLabel" : "今天", +"muharramLabel" : "穆哈拉姆", +"safarLabel" : "薩法爾", +"rabi1Label" : "拉比·阿瓦爾", +"rabi2Label" : "拉比阿爾塔尼", +"jumada1Label" : "朱馬達·阿瓦爾", +"jumada2Label" : "朱馬達·薩塔尼(Jumada al-thani)", +"rajabLabel" : "拉賈卜", +"shaabanLabel" : "沙阿班", +"ramadanLabel" : "齋月", +"shawwalLabel" : "肖瓦爾", +"dhualqiLabel" : "齊達", +"dhualhiLabel" : "杜·希賈", +"shortMuharramLabel" : "嗯", +"shortSafarLabel" : "Saf。", +"shortRabi1Label" : "拉比一世", +"shortRabi2Label" : "拉比II", +"shortJumada1Label" : "um一世", +"shortJumada2Label" : "um II", +"shortRajabLabel" : "拉吉", +"shortShaabanLabel" : "沙。", +"shortRamadanLabel" : "內存。", +"shortShawwalLabel" : "肖", +"shortDhualqiLabel" : "杜克", +"shortDhualhiLabel" : "杜赫" } \ 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 ef75f5484..844fa0dea 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zu.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zu.arb @@ -22,5 +22,29 @@ "allowedViewTimelineWeekLabel" : "Isikhathi seviki", "allowedViewTimelineWorkWeekLabel" : "Isikhathi seviki lomsebenzi", "allowedViewTimelineMonthLabel" : "Inyanga Yesikhathi", -"todayLabel" : "Namuhla" +"todayLabel" : "Namuhla", +"muharramLabel" : "UMuharram", +"safarLabel" : "I-Safar", +"rabi1Label" : "URabi 'al-awwal", +"rabi2Label" : "URabi 'al-thani", +"jumada1Label" : "Ijumada al-awwal", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "URajab", +"shaabanLabel" : "Sha'aban", +"ramadanLabel" : "I-Ramadan", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "UDhu al-Qi'dah", +"dhualhiLabel" : "UDhu al-Hijjah", +"shortMuharramLabel" : "UMuh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "URabi. Mina", +"shortRabi2Label" : "URabi. II", +"shortJumada1Label" : "NgeSonto. Mina", +"shortJumada2Label" : "NgeSonto. II", +"shortRajabLabel" : "URaj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "URam.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "I-Dhu'l-Q", +"shortDhualhiLabel" : "UDhu'l-H" } \ No newline at end of file diff --git a/packages/syncfusion_officechart/CHANGELOG.md b/packages/syncfusion_officechart/CHANGELOG.md index ad1055262..2e000e233 100644 --- a/packages/syncfusion_officechart/CHANGELOG.md +++ b/packages/syncfusion_officechart/CHANGELOG.md @@ -1,3 +1,9 @@ +## [Unreleased] + +**Features** + +* Provided support for adding the chart types: Area, AreaStacked, AreaStacked100, ColumnStacked100, BarStacked100, LineStacked100. + ## [18.3.35-beta.1] - 10/02/2020 **Features** diff --git a/packages/syncfusion_officechart/README.md b/packages/syncfusion_officechart/README.md index 35e03bf60..d0d73809b 100644 --- a/packages/syncfusion_officechart/README.md +++ b/packages/syncfusion_officechart/README.md @@ -27,6 +27,8 @@ The Excel package is a non-UI and reusable Flutter library to create different E - [Add line chart](#add-line-chart) - [Add stacked chart](#add-stacked-chart) - [Add chart elements](#add-chart-elements) + - [Add area chart](#add-area-chart) + - [Add stacked 100 chart](#add-stacked-100-chart) - [Support and feedback](#support-and-feedback) - [About Syncfusion](#about-syncfusion) @@ -41,6 +43,8 @@ The following are the chart features of Syncfusion Flutter OfficeChart. * Add bar chart to Excel worksheet. * Add stacked chart to Excel worksheet. * Add Chart elements. +* Add area chart to Excel worksheet. +* Add stacked100 chart to Excel worksheet. ## Get the demo application @@ -105,12 +109,13 @@ chart.dataRange = sheet.getRangeByName('A1:B4'); // set charts to worksheet. sheet.charts = charts; // save and dispose the workbook. -workbook.save('Chart.xlsx'); +List bytes = workbook.saveAsStream(); +File('Chart.xlsx').writeAsBytes(bytes); workbook.dispose(); ``` ### Add pie chart -Use the following code to add pie chart to excel worksheet. +Use the following code to add pie chart to Excel worksheet. ```dart // Create a new Excel document. @@ -141,13 +146,14 @@ chart1.isSeriesInRows = false; // set charts to worksheet. sheet.charts = charts; // save and dispose the workbook. -workbook.save('PieChart.xlsx'); +List bytes = workbook.saveAsStream(); +File('PieChart.xlsx').writeAsBytes(bytes); workbook.dispose(); ``` ### Add column chart -Use the following code to add column chart to excel worksheet. +Use the following code to add column chart to Excel worksheet. ```dart // Create a new Excel document. @@ -178,13 +184,14 @@ chart1.isSeriesInRows = false; // set charts to worksheet. sheet.charts = charts; // save and dispose the workbook. -workbook.save('ExcelColumnChart.xlsx'); +List bytes = workbook.saveAsStream(); +File('ExcelColumnChart.xlsx').writeAsBytes(bytes); workbook.dispose(); ``` ### Add bar chart -Use the following code to add bar chart to excel worksheet. +Use the following code to add bar chart to Excel worksheet. ```dart // Create a new Excel document. @@ -222,13 +229,14 @@ chart1.isSeriesInRows = false; // set charts to worksheet. sheet.charts = charts; // save and dispose the workbook. -workbook.save('BarChart.xlsx'); +List bytes = workbook.saveAsStream(); +File('BarChart.xlsx').writeAsBytes(bytes); workbook.dispose(); ``` ### Add line chart -Use the following code to add line chart to excel worksheet. +Use the following code to add line chart to Excel worksheet. ```dart // Create a new Excel document. @@ -260,7 +268,8 @@ chart.isSeriesInRows = false; // set charts to worksheet. sheet.charts = charts; //save and dispose workbook. -workbook.save('LineChart.xlsx'); +List bytes = workbook.saveAsStream(); +File('LineChart.xlsx').writeAsBytes(bytes); workbook.dispose(); ``` @@ -270,7 +279,7 @@ This section covers the various stacked chart. **Stacked Column chart** -Use the following code to add stacked column chart to excel worksheet. +Use the following code to add stacked column chart to Excel worksheet. ```dart // Create a new Excel document. @@ -308,13 +317,14 @@ chart1.isSeriesInRows = false; // set charts to worksheet. sheet.charts = charts; //save and dispose workbook. -workbook.save('ColunmStackedChart.xlsx'); +List bytes = workbook.saveAsStream(); +File('ColunmStackedChart.xlsx').writeAsBytes(bytes); workbook.dispose(); ``` **Stacked bar chart** -Use the following code to add stacked bar chart to excel worksheet. +Use the following code to add stacked bar chart to Excel worksheet. ```dart // Create a new Excel document. @@ -352,13 +362,14 @@ chart1.isSeriesInRows = false; // set charts to worksheet. sheet.charts = charts; //save and dispose workbook. -workbook.save('BarStackedChart.xlsx'); +List bytes = workbook.saveAsStream(); +File('BarStackedChart.xlsx').writeAsBytes(bytes); workbook.dispose(); ``` **Stacked Line chart** -Use the following code to add stacked line chart to excel worksheet. +Use the following code to add stacked line chart to Excel worksheet. ```dart // Create a new Excel document. @@ -396,12 +407,79 @@ chart1.isSeriesInRows = false; // set charts to worksheet. sheet.charts = charts; //save and dispose workbook. -workbook.save('LineStackedChart.xlsx'); +List bytes = workbook.saveAsStream(); +File('LineStackedChart.xlsx').writeAsBytes(bytes); workbook.dispose(); ``` + +**Stacked Area chart** + +Use the following code to add stacked area chart to Excel worksheet. + +```dart +// Create a new Excel document. +final Workbook workbook = Workbook(); +// Accessing worksheet via index. +final Worksheet sheet = workbook.worksheets[0]; +// Setting value in the cell. +sheet.getRangeByName('A1').text = 'Fruits'; +sheet.getRangeByName('A2').text = 'Apples'; +sheet.getRangeByName('A3').text = 'Grapes'; +sheet.getRangeByName('A4').text = 'Bananas'; +sheet.getRangeByName('A5').text = 'Oranges'; +sheet.getRangeByName('A6').text = 'Melons'; +sheet.getRangeByName('B1').text = 'Joey'; +sheet.getRangeByName('B2').number = 5; +sheet.getRangeByName('B3').number = 4; +sheet.getRangeByName('B4').number = 4; +sheet.getRangeByName('B5').number = 2; +sheet.getRangeByName('B6').number = 2; +sheet.getRangeByName('C1').text = 'Mathew'; +sheet.getRangeByName('C2').number = 3; +sheet.getRangeByName('C3').number = 5; +sheet.getRangeByName('C4').number = 4; +sheet.getRangeByName('C5').number = 1; +sheet.getRangeByName('C6').number = 7; +sheet.getRangeByName('D1').text = 'Peter'; +sheet.getRangeByName('D2').number = 2; +sheet.getRangeByName('D3').number = 2; +sheet.getRangeByName('D4').number = 3; +sheet.getRangeByName('D5').number = 5; +sheet.getRangeByName('D6').number = 6; + +// Create an instances of chart collection. +final ChartCollection charts = ChartCollection(sheet); +// Add the chart. +final Chart chart = charts.add(); +// Set Chart Type. +chart.chartType = ExcelChartType.areaStacked; +// Set data range in the worksheet. +chart.dataRange = sheet.getRangeByName('A1:D6'); +chart.isSeriesInRows = false; + +//Set Chart Title +chart.chartTitle = 'Stacked Area Chart'; + +//Set Legend +chart.hasLegend = true; +chart.legend.position = ExcelLegendPosition.bottom; + +//Positioning the chart in the worksheet +chart.topRow = 8; +chart.leftColumn = 1; +chart.bottomRow = 23; +chart.rightColumn = 8; +// set charts to worksheet. +sheet.charts = charts; +// save and dispose the workbook. +final List bytes = workbook.saveAsStream(); +workbook.dispose(); +File('AreaStackedChart.xlsx').writeAsBytes(bytes); +``` + ### Add chart elements -Use the following code to add chart elements to excel worksheet. +Use the following code to add chart elements to Excel worksheet. ```dart // Create a new Excel document. @@ -461,9 +539,341 @@ serie.dataLabels.textArea.fontName = 'Arial'; // set charts to worksheet. sheet.charts = charts; //save and dispose workbook. -workbook.save('ChartElement.xlsx'); +List bytes = workbook.saveAsStream(); +File('ChartElement.xlsx').writeAsBytes(bytes); +workbook.dispose(); +``` + +### Add area chart + +Use the following code to add area chart to Excel worksheet. + +```dart +// Create a new Excel document. +final Workbook workbook = Workbook(); +// Accessing worksheet via index. +final Worksheet sheet = workbook.worksheets[0]; +// Setting value in the cell. +sheet.getRangeByName('A1').text = 'Fruits'; +sheet.getRangeByName('A2').text = 'Apples'; +sheet.getRangeByName('A3').text = 'Grapes'; +sheet.getRangeByName('A4').text = 'Bananas'; +sheet.getRangeByName('A5').text = 'Oranges'; +sheet.getRangeByName('A6').text = 'Melons'; +sheet.getRangeByName('B1').text = 'Joey'; +sheet.getRangeByName('B2').number = 5; +sheet.getRangeByName('B3').number = 4; +sheet.getRangeByName('B4').number = 4; +sheet.getRangeByName('B5').number = 2; +sheet.getRangeByName('B6').number = 2; +sheet.getRangeByName('C1').text = 'Mathew'; +sheet.getRangeByName('C2').number = 3; +sheet.getRangeByName('C3').number = 5; +sheet.getRangeByName('C4').number = 4; +sheet.getRangeByName('C5').number = 1; +sheet.getRangeByName('C6').number = 7; +sheet.getRangeByName('D1').text = 'Peter'; +sheet.getRangeByName('D2').number = 2; +sheet.getRangeByName('D3').number = 2; +sheet.getRangeByName('D4').number = 3; +sheet.getRangeByName('D5').number = 5; +sheet.getRangeByName('D6').number = 6; + +// Create an instances of chart collection. +final ChartCollection charts = ChartCollection(sheet); +// Add the chart. +final Chart chart = charts.add(); +// Set Chart Type. +chart.chartType = ExcelChartType.area; +// Set data range in the worksheet. +chart.dataRange = sheet.getRangeByName('A1:D6'); +chart.isSeriesInRows = false; + +//Set Chart Title +chart.chartTitle = 'Area Chart'; + +//Set Legend +chart.hasLegend = true; +chart.legend.position = ExcelLegendPosition.bottom; + +//Positioning the chart in the worksheet +chart.topRow = 8; +chart.leftColumn = 1; +chart.bottomRow = 23; +chart.rightColumn = 8; +// set charts to worksheet. +sheet.charts = charts; +// save and dispose the workbook. +final List bytes = workbook.saveAsStream(); +workbook.dispose(); +File('AreaChart.xlsx').writeAsBytes(bytes); +``` + +### Add stacked 100 chart + +This section covers the various stacked 100 chart. + +**Stacked 100 Column chart** + +Use the following code to add stacked 100 column chart to Excel worksheet. + +```dart +// Create a new Excel document. +final Workbook workbook = Workbook(); +// Accessing worksheet via index. +final Worksheet sheet = workbook.worksheets[0]; +// Setting value in the cell. +sheet.getRangeByName('A1').text = 'Fruits'; +sheet.getRangeByName('A2').text = 'Apples'; +sheet.getRangeByName('A3').text = 'Grapes'; +sheet.getRangeByName('A4').text = 'Bananas'; +sheet.getRangeByName('A5').text = 'Oranges'; +sheet.getRangeByName('A6').text = 'Melons'; +sheet.getRangeByName('B1').text = 'Joey'; +sheet.getRangeByName('B2').number = 5; +sheet.getRangeByName('B3').number = 4; +sheet.getRangeByName('B4').number = 4; +sheet.getRangeByName('B5').number = 2; +sheet.getRangeByName('B6').number = 2; +sheet.getRangeByName('C1').text = 'Mathew'; +sheet.getRangeByName('C2').number = 3; +sheet.getRangeByName('C3').number = 5; +sheet.getRangeByName('C4').number = 4; +sheet.getRangeByName('C5').number = 1; +sheet.getRangeByName('C6').number = 7; +sheet.getRangeByName('D1').text = 'Peter'; +sheet.getRangeByName('D2').number = 2; +sheet.getRangeByName('D3').number = 2; +sheet.getRangeByName('D4').number = 3; +sheet.getRangeByName('D5').number = 5; +sheet.getRangeByName('D6').number = 6; + +// Create an instances of chart collection. +final ChartCollection charts = ChartCollection(sheet); +// Add the chart. +final Chart chart = charts.add(); +// Set Chart Type. +chart.chartType = ExcelChartType.columnStacked100; +// Set data range in the worksheet. +chart.dataRange = sheet.getRangeByName('A1:D6'); +chart.isSeriesInRows = false; + +//Set Chart Title +chart.chartTitle = 'Stacked 100 Column Chart'; + +//Set Legend +chart.hasLegend = true; +chart.legend.position = ExcelLegendPosition.bottom; + +//Positioning the chart in the worksheet +chart.topRow = 8; +chart.leftColumn = 1; +chart.bottomRow = 23; +chart.rightColumn = 8; +// set charts to worksheet. +sheet.charts = charts; +// save and dispose the workbook. +final List bytes = workbook.saveAsStream(); workbook.dispose(); +File('ColumnStacked100Chart.xlsx').writeAsBytes(bytes); ``` + +**Stacked 100 bar chart** + +Use the following code to add stacked 100 bar chart to Excel worksheet. + +```dart +// Create a new Excel document. +final Workbook workbook = Workbook(); +// Accessing worksheet via index. +final Worksheet sheet = workbook.worksheets[0]; +// Setting value in the cell. +sheet.getRangeByName('A1').text = 'Fruits'; +sheet.getRangeByName('A2').text = 'Apples'; +sheet.getRangeByName('A3').text = 'Grapes'; +sheet.getRangeByName('A4').text = 'Bananas'; +sheet.getRangeByName('A5').text = 'Oranges'; +sheet.getRangeByName('A6').text = 'Melons'; +sheet.getRangeByName('B1').text = 'Joey'; +sheet.getRangeByName('B2').number = 5; +sheet.getRangeByName('B3').number = 4; +sheet.getRangeByName('B4').number = 4; +sheet.getRangeByName('B5').number = 2; +sheet.getRangeByName('B6').number = 2; +sheet.getRangeByName('C1').text = 'Mathew'; +sheet.getRangeByName('C2').number = 3; +sheet.getRangeByName('C3').number = 5; +sheet.getRangeByName('C4').number = 4; +sheet.getRangeByName('C5').number = 1; +sheet.getRangeByName('C6').number = 7; +sheet.getRangeByName('D1').text = 'Peter'; +sheet.getRangeByName('D2').number = 2; +sheet.getRangeByName('D3').number = 2; +sheet.getRangeByName('D4').number = 3; +sheet.getRangeByName('D5').number = 5; +sheet.getRangeByName('D6').number = 6; + +// Create an instances of chart collection. +final ChartCollection charts = ChartCollection(sheet); +// Add the chart. +final Chart chart = charts.add(); +// Set Chart Type. +chart.chartType = ExcelChartType.barStacked100; +// Set data range in the worksheet. +chart.dataRange = sheet.getRangeByName('A1:D6'); +chart.isSeriesInRows = false; + +//Set Chart Title +chart.chartTitle = 'Stacked 100 bar Chart'; + +//Set Legend +chart.hasLegend = true; +chart.legend.position = ExcelLegendPosition.bottom; + +//Positioning the chart in the worksheet +chart.topRow = 8; +chart.leftColumn = 1; +chart.bottomRow = 23; +chart.rightColumn = 8; +// set charts to worksheet. +sheet.charts = charts; +// save and dispose the workbook. +final List bytes = workbook.saveAsStream(); +workbook.dispose(); +File('BarStacked100Chart.xlsx').writeAsBytes(bytes); +``` + +**Stacked 100 Line chart** + +Use the following code to add stacked 100 line chart to Excel worksheet. + +```dart +// Create a new Excel document. +final Workbook workbook = Workbook(); +// Accessing worksheet via index. +final Worksheet sheet = workbook.worksheets[0]; +// Setting value in the cell. +sheet.getRangeByName('A1').text = 'Fruits'; +sheet.getRangeByName('A2').text = 'Apples'; +sheet.getRangeByName('A3').text = 'Grapes'; +sheet.getRangeByName('A4').text = 'Bananas'; +sheet.getRangeByName('A5').text = 'Oranges'; +sheet.getRangeByName('A6').text = 'Melons'; +sheet.getRangeByName('B1').text = 'Joey'; +sheet.getRangeByName('B2').number = 5; +sheet.getRangeByName('B3').number = 4; +sheet.getRangeByName('B4').number = 4; +sheet.getRangeByName('B5').number = 2; +sheet.getRangeByName('B6').number = 2; +sheet.getRangeByName('C1').text = 'Mathew'; +sheet.getRangeByName('C2').number = 3; +sheet.getRangeByName('C3').number = 5; +sheet.getRangeByName('C4').number = 4; +sheet.getRangeByName('C5').number = 1; +sheet.getRangeByName('C6').number = 7; +sheet.getRangeByName('D1').text = 'Peter'; +sheet.getRangeByName('D2').number = 2; +sheet.getRangeByName('D3').number = 2; +sheet.getRangeByName('D4').number = 3; +sheet.getRangeByName('D5').number = 5; +sheet.getRangeByName('D6').number = 6; + +// Create an instances of chart collection. +final ChartCollection charts = ChartCollection(sheet); +// Add the chart. +final Chart chart = charts.add(); +// Set Chart Type. +chart.chartType = ExcelChartType.lineStacked100; +// Set data range in the worksheet. +chart.dataRange = sheet.getRangeByName('A1:D6'); +chart.isSeriesInRows = false; + +//Set Chart Title +chart.chartTitle = 'Stacked 100 line Chart'; + +//Set Legend +chart.hasLegend = true; +chart.legend.position = ExcelLegendPosition.bottom; + +//Positioning the chart in the worksheet +chart.topRow = 8; +chart.leftColumn = 1; +chart.bottomRow = 23; +chart.rightColumn = 8; +// set charts to worksheet. +sheet.charts = charts; +// save and dispose the workbook. +final List bytes = workbook.saveAsStream(); +workbook.dispose(); +File('lineStacked100Chart.xlsx').writeAsBytes(bytes); + +``` + +**Stacked 100 Area chart** + +Use the following code to add stacked 100 area chart to Excel worksheet. + +```dart +// Create a new Excel document. +final Workbook workbook = Workbook(); +// Accessing worksheet via index. +final Worksheet sheet = workbook.worksheets[0]; +// Setting value in the cell. +sheet.getRangeByName('A1').text = 'Fruits'; +sheet.getRangeByName('A2').text = 'Apples'; +sheet.getRangeByName('A3').text = 'Grapes'; +sheet.getRangeByName('A4').text = 'Bananas'; +sheet.getRangeByName('A5').text = 'Oranges'; +sheet.getRangeByName('A6').text = 'Melons'; +sheet.getRangeByName('B1').text = 'Joey'; +sheet.getRangeByName('B2').number = 5; +sheet.getRangeByName('B3').number = 4; +sheet.getRangeByName('B4').number = 4; +sheet.getRangeByName('B5').number = 2; +sheet.getRangeByName('B6').number = 2; +sheet.getRangeByName('C1').text = 'Mathew'; +sheet.getRangeByName('C2').number = 3; +sheet.getRangeByName('C3').number = 5; +sheet.getRangeByName('C4').number = 4; +sheet.getRangeByName('C5').number = 1; +sheet.getRangeByName('C6').number = 7; +sheet.getRangeByName('D1').text = 'Peter'; +sheet.getRangeByName('D2').number = 2; +sheet.getRangeByName('D3').number = 2; +sheet.getRangeByName('D4').number = 3; +sheet.getRangeByName('D5').number = 5; +sheet.getRangeByName('D6').number = 6; + +// Create an instances of chart collection. +final ChartCollection charts = ChartCollection(sheet); +// Add the chart. +final Chart chart = charts.add(); +// Set Chart Type. +chart.chartType = ExcelChartType.areaStacked100; +// Set data range in the worksheet. +chart.dataRange = sheet.getRangeByName('A1:D6'); +chart.isSeriesInRows = false; + +//Set Chart Title +chart.chartTitle = 'Stacked 100 Area Chart'; + +//Set Legend +chart.hasLegend = true; +chart.legend.position = ExcelLegendPosition.bottom; + +//Positioning the chart in the worksheet +chart.topRow = 8; +chart.leftColumn = 1; +chart.bottomRow = 23; +chart.rightColumn = 8; +// set charts to worksheet. +sheet.charts = charts; +// save and dispose the workbook. +final List bytes = workbook.saveAsStream(); +workbook.dispose(); +File('AreaStacked100Chart.xlsx').writeAsBytes(bytes); +``` + ## Support and feedback * For any other queries, contact our [Syncfusion support team](https://www.syncfusion.com/support/directtrac/incidents/newincident) or post the queries through the [Community forums](https://www.syncfusion.com/forums). You can also submit a feature request or a bug through our [Feedback portal](https://www.syncfusion.com/feedback/flutter). diff --git a/packages/syncfusion_officechart/example/lib/main.dart b/packages/syncfusion_officechart/example/lib/main.dart index 965807d86..5dbab4890 100644 --- a/packages/syncfusion_officechart/example/lib/main.dart +++ b/packages/syncfusion_officechart/example/lib/main.dart @@ -63,7 +63,7 @@ class _CreateOfficeChartState extends State { final Workbook workbook = Workbook(0); //Adding a Sheet with name to workbook. final Worksheet sheet1 = workbook.worksheets.addWithName('Budget'); - sheet1.showGridLines = false; + sheet1.showGridlines = false; sheet1.enableSheetCalculations(); sheet1.getRangeByIndex(1, 1).columnWidth = 19.86; @@ -166,7 +166,7 @@ class _CreateOfficeChartState extends State { chart.rightColumn = 5; sheet1.charts = charts; - final List bytes = workbook.saveStream(); + final List bytes = workbook.saveAsStream(); workbook.dispose(); //Get the storage folder location using path_provider package. diff --git a/packages/syncfusion_officechart/lib/officechart.dart b/packages/syncfusion_officechart/lib/officechart.dart index 9b65fa6f4..eba5345e2 100644 --- a/packages/syncfusion_officechart/lib/officechart.dart +++ b/packages/syncfusion_officechart/lib/officechart.dart @@ -2,8 +2,6 @@ library officechart; import 'dart:convert'; import 'dart:core'; -// ignore: unused_import -import 'dart:io'; import 'package:syncfusion_flutter_xlsio/xlsio.dart'; import 'package:archive/archive.dart'; 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 3152a4dc3..9f29268c2 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_category_axis.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_category_axis.dart @@ -16,6 +16,10 @@ class ChartCategoryAxis extends ChartAxis { value = _sheet; } + /// True to cut unused plot area. otherwise False. Default for area and surface charts. + // ignore: prefer_final_fields + bool _isBetween = false; + /// Parent chart. Chart _chart; diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_collection.dart b/packages/syncfusion_officechart/lib/src/chart/chart_collection.dart index 5e106d8ae..db8d68755 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_collection.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_collection.dart @@ -49,13 +49,16 @@ class ChartCollection extends ChartHelper { /// ChartCollection chart = ChartCollection(sheet); /// chart.add(); /// sheet.charts = chart; - /// workbook.save('EmptyChart.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('EmptyChart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` 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); innerList.add(chart); chart.name = 'Chart' + innerList.length.toString(); diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_enum.dart b/packages/syncfusion_officechart/lib/src/chart/chart_enum.dart index 043b3baa6..a81dff8cc 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_enum.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_enum.dart @@ -5,23 +5,41 @@ enum ExcelChartType { /// Represents the clustered column chart. column, - /// Represents the line chart. - line, + /// Represents the column stacked chart. + columnStacked, + + /// Represents the 100% stacked column chart. + columnStacked100, /// Represents the clustered bar chart. bar, - /// Represents the Pie chart. - pie, - /// Represents the bar stacked chart. barStacked, - /// Represents the column stacked chart. - columnStacked, + /// Represents the 100% stacked bar chart. + barStacked100, + + /// Represents the line chart. + line, /// Represents the line stacked chart. lineStacked, + + /// Represents the 100% stacked line chart. + lineStacked100, + + /// Represents the Pie chart. + pie, + + /// Represents the area chart. + area, + + /// Represents the stacked area chart. + areaStacked, + + /// Represents the 100% stacked area chart. + areaStacked100 } /// Specifies the line pattern for the border. diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_impl.dart b/packages/syncfusion_officechart/lib/src/chart/chart_impl.dart index e8b37cd95..778bf7b1f 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_impl.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_impl.dart @@ -49,7 +49,7 @@ class Chart { /// Represent the clustered chart collection. final List _chartsCluster = [ ExcelChartType.bar, - ExcelChartType.column, + ExcelChartType.column ]; /// Represent the stacked chart collection. @@ -57,8 +57,20 @@ class Chart { ExcelChartType.barStacked, ExcelChartType.columnStacked, ExcelChartType.lineStacked, + ExcelChartType.areaStacked, ]; + /// Represent 100% charts.Here each value in a series is shown as a portion of 100%. + final List _charts100 = [ + ExcelChartType.columnStacked100, + ExcelChartType.barStacked100, + ExcelChartType.lineStacked100, + ExcelChartType.areaStacked100 + ]; + + /// Chart type. + ExcelChartType _chartType = ExcelChartType.column; + /// Represent chart index. int index; @@ -80,7 +92,9 @@ class Chart { /// chart.dataRange = sheet.getRangeByName('A1:B4'); /// chart.topRow = 8; /// sheet.charts = charts; - /// workbook.save('Chart.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` int topRow; @@ -102,7 +116,9 @@ class Chart { /// chart.dataRange = sheet.getRangeByName('A1:B4'); /// chart.leftColumn = 4; /// sheet.charts = charts; - /// workbook.save('Chart.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` int leftColumn; @@ -124,7 +140,9 @@ class Chart { /// chart.dataRange = sheet.getRangeByName('A1:B4'); /// chart.bottomRow = 10; /// sheet.charts = charts; - /// workbook.save('Chart.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` int bottomRow; @@ -146,7 +164,9 @@ class Chart { /// chart.dataRange = sheet.getRangeByName('A1:B4'); /// chart.rightColumn = 8; /// sheet.charts = charts; - /// workbook.save('Chart.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` int rightColumn; @@ -169,9 +189,20 @@ class Chart { /// chart.dataRange = sheet.getRangeByName('A1:B4'); /// chart.isSeriesInRows = false; /// sheet.charts = charts; - /// workbook.save('ChartType.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('ChartType.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` - ExcelChartType chartType = ExcelChartType.bar; + ExcelChartType get chartType { + return _chartType; + } + + set chartType(ExcelChartType value) { + _chartType = value; + if (!_chartType.toString().contains('area')) { + _primaryCategoryAxis._isBetween = true; + } + } /// Gets the boolean value to display the chart legend, True by default. bool get hasLegend { @@ -197,7 +228,8 @@ class Chart { /// chart.dataRange = sheet.getRangeByName('A1:B4'); /// chart.hasLegend = false; /// sheet.charts = charts; - /// workbook.save('ChartHasLegend.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('ChartHasLegend.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` set hasLegend(bool value) { @@ -226,7 +258,9 @@ class Chart { /// chart.dataRange = sheet.getRangeByName('A1:B4'); /// chart.linePattern = ExcelChartLinePattern.dashDot; /// sheet.charts = charts; - /// workbook.save('ChartLinePattern.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('ChartLinePattern.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` ExcelChartLinePattern linePattern = ExcelChartLinePattern.none; @@ -249,7 +283,9 @@ class Chart { /// chart.linePattern = ExcelChartLinePattern.dashDot; /// chart.linePatternColor = "#FFFF00"; /// sheet.charts = charts; - /// workbook.save('ChartLineColor.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('ChartLineColor.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` String linePatternColor; @@ -271,7 +307,9 @@ class Chart { /// chart.dataRange = sheet.getRangeByName('A1:B4'); /// chart.legend.position = ExcelLegendPosition.bottom; /// sheet.charts = charts; - /// workbook.save('ChartLegendPosition.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('ChartLegendPosition.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` ChartLegend get legend { return _legend; @@ -295,7 +333,9 @@ class Chart { /// chart.name = "Sales"; /// chart.dataRange = sheet.getRangeByName('A1:B4'); /// sheet.charts = charts; - /// workbook.save('ChartName.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('ChartName.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` String name; @@ -321,7 +361,9 @@ class Chart { /// chart.chartTitleArea.size = 15; /// chart.chartTitleArea.fontName = 'Arial'; /// sheet.charts = charts; - /// workbook.save('ChartTitleArea.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('ChartTitleArea.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` ChartTextArea get chartTitleArea { if (_textArea == null) _createChartTitle(); @@ -352,7 +394,9 @@ class Chart { /// chart.dataRange = sheet.getRangeByName('A1:B4'); /// chart.chartTitle = 'Sales'; /// sheet.charts = charts; - /// workbook.save('ChartTitle.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('ChartTitle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` set chartTitle(String title) { chartTitleArea.text = title; @@ -388,7 +432,9 @@ class Chart { /// chart.dataRange = sheet.getRangeByName('A1:B4'); /// chart.hasTitle = true; /// sheet.charts = charts; - /// workbook.save('ChartHasTitle.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('ChartHasTitle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` set hasTitle(bool value) { if (_textArea != null) { @@ -438,7 +484,9 @@ class Chart { /// chart.dataRange = sheet.getRangeByName('A1:B4'); /// chart.isSeriesInRows = false; /// sheet.charts = charts; - /// workbook.save('Chart.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` set isSeriesInRows(bool value) { final int iCount = _series.count; @@ -482,7 +530,9 @@ class Chart { /// chart.plotArea.linePattern = ExcelChartLinePattern.dashDot; /// chart.plotArea.linePatternColor = '#0000FF'; /// sheet.charts = charts; - /// workbook.save('Chart.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` ChartPlotArea get plotArea { return _plotArea; @@ -509,7 +559,9 @@ class Chart { /// chart.primaryCategoryAxis.title = 'X axis'; /// chart.primaryCategoryAxis.titleArea.bold = true; /// sheet.charts = charts; - /// workbook.save('Chart.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` ChartCategoryAxis get primaryCategoryAxis { return _primaryCategoryAxis; @@ -536,7 +588,9 @@ class Chart { /// chart.primaryValueAxis.title = 'Y axis'; /// chart.primaryValueAxis.titleArea.bold = true; /// sheet.charts = charts; - /// workbook.save('Chart.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` ChartValueAxis get primaryValueAxis { return _primaryValueAxis; @@ -570,7 +624,9 @@ class Chart { /// chart.chartType = ExcelChartType.bar; /// chart.dataRange = sheet.getRangeByName('A1:B4'); /// sheet.charts = charts; - /// workbook.save('Chart.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` set dataRange(Range value) { if (_dataRange != value) { @@ -788,4 +844,9 @@ class Chart { bool _getIsClustered(ExcelChartType chartType) { return (_chartsCluster.contains(chartType)); } + + /// Indicates whether if given chart type is clustered chart or not. + bool _getIs100(ExcelChartType chartType) { + return (_charts100.contains(chartType)); + } } diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_serialization.dart b/packages/syncfusion_officechart/lib/src/chart/chart_serialization.dart index 004047370..c8c4ad191 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_serialization.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_serialization.dart @@ -169,8 +169,8 @@ class ChartSerialization { builder.element('a:p', nest: () { builder.element('a:pPr', nest: () { builder.element('a:defRPr', nest: () { - final int size = textArea.size * 100; - builder.attribute('sz', size.toString()); + 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 != null) { @@ -251,8 +251,8 @@ class ChartSerialization { XmlBuilder builder, ChartTextArea textArea) { builder.element('a:rPr', nest: () { builder.attribute('lang', 'en-US'); - final int size = textArea.size * 100; - builder.attribute('sz', size.toString()); + 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'); @@ -297,17 +297,26 @@ class ChartSerialization { if (chart == null) throw ("chart - Value can't be null"); switch (chart.chartType) { case ExcelChartType.column: + case ExcelChartType.columnStacked: + case ExcelChartType.columnStacked100: case ExcelChartType.bar: case ExcelChartType.barStacked: - case ExcelChartType.columnStacked: + case ExcelChartType.barStacked100: _serializeBarChart(builder, chart); break; case ExcelChartType.line: case ExcelChartType.lineStacked: + case ExcelChartType.lineStacked100: _serializeLineChart(builder, chart); break; + case ExcelChartType.area: + case ExcelChartType.areaStacked: + case ExcelChartType.areaStacked100: + _serializeAreaChart(builder, chart); + break; + case ExcelChartType.pie: _serializePieChart(builder, chart); break; @@ -329,9 +338,6 @@ class ChartSerialization { final ChartSerie firstSerie = chart.series[i]; _serializeSerie(builder, firstSerie); } - builder.element('c:gapWidth', nest: () { - builder.attribute('val', 150); - }); builder.element('c:axId', nest: () { builder.attribute('val', 59983360); }); @@ -350,7 +356,7 @@ class ChartSerialization { builder.element('c:barChart', nest: () { final String strDirection = - chart.chartType.toString().contains('Bar') ? 'bar' : 'col'; + chart.chartType.toString().contains('bar') ? 'bar' : 'col'; builder.element('c:barDir', nest: () { builder.attribute('val', strDirection); }); @@ -365,7 +371,8 @@ class ChartSerialization { builder.element('c:gapWidth', nest: () { builder.attribute('val', 150); }); - if (chart._getIsStacked(chart.chartType)) { + if (chart._getIsStacked(chart.chartType) || + chart._getIs100(chart.chartType)) { builder.element('c:overlap', nest: () { builder.attribute('val', '100'); }); @@ -380,11 +387,38 @@ class ChartSerialization { _serializeAxes(builder, chart); } + /// serializes Area chart. + void _serializeAreaChart(XmlBuilder builder, Chart chart) { + if (builder == null) throw ("writer - Value can't be null"); + + if (chart == null) throw ("chart - Value can't be null"); + + builder.element('c:areaChart', nest: () { + _serializeChartGrouping(builder, chart); + builder.element('c:varyColors', nest: () { + 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)) { strGrouping = 'clustered'; + } else if (chart._getIs100(chart.chartType)) { + strGrouping = 'percentStacked'; } else if (chart._getIsStacked(chart.chartType)) { strGrouping = 'stacked'; } else { @@ -582,8 +616,8 @@ class ChartSerialization { builder.element('a:p', nest: () { builder.element('a:pPr', nest: () { builder.element('a:defRPr', nest: () { - final int size = textArea.size * 100; - builder.attribute('sz', size.toString()); + 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'); @@ -878,8 +912,11 @@ class ChartSerialization { 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', 'between'); + builder.attribute('val', strCrossBetween); }); }); } diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_serie.dart b/packages/syncfusion_officechart/lib/src/chart/chart_serie.dart index 269cfe3a2..6190bbc54 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_serie.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_serie.dart @@ -75,7 +75,9 @@ class ChartSerie { /// serie.linePattern = ExcelChartLinePattern.dash; /// chart.isSeriesInRows = false; /// sheet.charts = charts; - /// workbook.save('Chart.xlsx'); + /// List bytes = workbook.saveAsStream(); + /// File('Chart.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); /// ``` ChartDataLabels get dataLabels { return _dataLabels ??= ChartDataLabels(this); 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 60bb1b319..b1f77caf1 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_text_area.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_text_area.dart @@ -59,12 +59,12 @@ class ChartTextArea { } /// Gets font size. - int get size { + double get size { return _font.size; } /// Sets font size. - set size(int value) { + set size(double value) { _font.size = value; } diff --git a/packages/syncfusion_officecore/example/README.md b/packages/syncfusion_officecore/example/README.md new file mode 100644 index 000000000..cdc9501a2 --- /dev/null +++ b/packages/syncfusion_officecore/example/README.md @@ -0,0 +1,3 @@ +# officecore_example + +Demo for creating a Excel file using syncfusion_flutter_xlsio and syncfusion_officecore packages. diff --git a/packages/syncfusion_officecore/example/analysis_options.yaml b/packages/syncfusion_officecore/example/analysis_options.yaml new file mode 100644 index 000000000..2a22b53a9 --- /dev/null +++ b/packages/syncfusion_officecore/example/analysis_options.yaml @@ -0,0 +1,6 @@ +include: package:syncfusion_flutter_core/analysis_options.yaml + +analyzer: + errors: + include_file_not_found: ignore + lines_longer_than_80_chars: ignore diff --git a/packages/syncfusion_officecore/example/android/.gitignore b/packages/syncfusion_officecore/example/android/.gitignore new file mode 100644 index 000000000..bc2100d8f --- /dev/null +++ b/packages/syncfusion_officecore/example/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/packages/syncfusion_officecore/example/android/app/build.gradle b/packages/syncfusion_officecore/example/android/app/build.gradle new file mode 100644 index 000000000..ccd7de9b4 --- /dev/null +++ b/packages/syncfusion_officecore/example/android/app/build.gradle @@ -0,0 +1,63 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.xlsio_example" + minSdkVersion 16 + targetSdkVersion 28 + 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_officecore/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_officecore/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..df91c765e --- /dev/null +++ b/packages/syncfusion_officecore/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/syncfusion_officecore/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_officecore/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..3ab96c3ba --- /dev/null +++ b/packages/syncfusion_officecore/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_officecore/example/android/app/src/main/kotlin/com/example/officecore_example/MainActivity.kt b/packages/syncfusion_officecore/example/android/app/src/main/kotlin/com/example/officecore_example/MainActivity.kt new file mode 100644 index 000000000..e4d0b5027 --- /dev/null +++ b/packages/syncfusion_officecore/example/android/app/src/main/kotlin/com/example/officecore_example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.officecore_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/drawable/launch_background.xml b/packages/syncfusion_officecore/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/syncfusion_officecore/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 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 new file mode 100644 index 000000000..1f83a33fd --- /dev/null +++ b/packages/syncfusion_officecore/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_officecore/example/android/app/src/profile/AndroidManifest.xml b/packages/syncfusion_officecore/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..df91c765e --- /dev/null +++ b/packages/syncfusion_officecore/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/syncfusion_officecore/example/android/build.gradle b/packages/syncfusion_officecore/example/android/build.gradle new file mode 100644 index 000000000..3100ad2d5 --- /dev/null +++ b/packages/syncfusion_officecore/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/syncfusion_officecore/example/android/gradle.properties b/packages/syncfusion_officecore/example/android/gradle.properties new file mode 100644 index 000000000..38c8d4544 --- /dev/null +++ b/packages/syncfusion_officecore/example/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +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 new file mode 100644 index 000000000..296b146b7 --- /dev/null +++ b/packages/syncfusion_officecore/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/packages/syncfusion_officecore/example/android/officecore_example_android.iml b/packages/syncfusion_officecore/example/android/officecore_example_android.iml new file mode 100644 index 000000000..1029d721f --- /dev/null +++ b/packages/syncfusion_officecore/example/android/officecore_example_android.iml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_officecore/example/android/settings.gradle b/packages/syncfusion_officecore/example/android/settings.gradle new file mode 100644 index 000000000..5a2f14fb1 --- /dev/null +++ b/packages/syncfusion_officecore/example/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/packages/syncfusion_officecore/example/ios/.gitignore b/packages/syncfusion_officecore/example/ios/.gitignore new file mode 100644 index 000000000..e96ef602b --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/syncfusion_officecore/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_officecore/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..6b4c0f78a --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/packages/syncfusion_officecore/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_officecore/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/syncfusion_officecore/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_officecore/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..6b50bd568 --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,506 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 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 = ( + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + 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 = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample; + 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; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + 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 = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + 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 = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample; + 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_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + 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 new file mode 100644 index 000000000..a28140cfd --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_officecore/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_officecore/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/packages/syncfusion_officecore/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_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/packages/syncfusion_officecore/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_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 zcmeHN2~<R zh`|8`A_PQ1nSu(UMFx?8j8PC!!VDphaL#`F42fd#7Vlc`zIE4n%Y~eiz4y1j|NDpi z?<@|pSJ-HM`qifhf@m%MamgwK83`XpBA<+azdF#2QsT{X@z0A9Bq>~TVErigKH1~P zRX-!h-f0NJ4Mh++{D}J+K>~~rq}d%o%+4dogzXp7RxX4C>Km5XEI|PAFDmo;DFm6G zzjVoB`@qW98Yl0Kvc-9w09^PrsobmG*Eju^=3f?0o-t$U)TL1B3;sZ^!++3&bGZ!o-*6w?;oOhf z=A+Qb$scV5!RbG+&2S}BQ6YH!FKb0``VVX~T$dzzeSZ$&9=X$3)_7Z{SspSYJ!lGE z7yig_41zpQ)%5dr4ff0rh$@ky3-JLRk&DK)NEIHecf9c*?Z1bUB4%pZjQ7hD!A0r-@NF(^WKdr(LXj|=UE7?gBYGgGQV zidf2`ZT@pzXf7}!NH4q(0IMcxsUGDih(0{kRSez&z?CFA0RVXsVFw3^u=^KMtt95q z43q$b*6#uQDLoiCAF_{RFc{!H^moH_cmll#Fc^KXi{9GDl{>%+3qyfOE5;Zq|6#Hb zp^#1G+z^AXfRKaa9HK;%b3Ux~U@q?xg<2DXP%6k!3E)PA<#4$ui8eDy5|9hA5&{?v z(-;*1%(1~-NTQ`Is1_MGdQ{+i*ccd96ab$R$T3=% zw_KuNF@vI!A>>Y_2pl9L{9h1-C6H8<)J4gKI6{WzGBi<@u3P6hNsXG=bRq5c+z;Gc3VUCe;LIIFDmQAGy+=mRyF++u=drBWV8-^>0yE9N&*05XHZpPlE zxu@?8(ZNy7rm?|<+UNe0Vs6&o?l`Pt>P&WaL~M&#Eh%`rg@Mbb)J&@DA-wheQ>hRV z<(XhigZAT z>=M;URcdCaiO3d^?H<^EiEMDV+7HsTiOhoaMX%P65E<(5xMPJKxf!0u>U~uVqnPN7T!X!o@_gs3Ct1 zlZ_$5QXP4{Aj645wG_SNT&6m|O6~Tsl$q?nK*)(`{J4b=(yb^nOATtF1_aS978$x3 zx>Q@s4i3~IT*+l{@dx~Hst21fR*+5}S1@cf>&8*uLw-0^zK(+OpW?cS-YG1QBZ5q! zgTAgivzoF#`cSz&HL>Ti!!v#?36I1*l^mkrx7Y|K6L#n!-~5=d3;K<;Zqi|gpNUn_ z_^GaQDEQ*jfzh;`j&KXb66fWEk1K7vxQIMQ_#Wu_%3 z4Oeb7FJ`8I>Px;^S?)}2+4D_83gHEq>8qSQY0PVP?o)zAv3K~;R$fnwTmI-=ZLK`= zTm+0h*e+Yfr(IlH3i7gUclNH^!MU>id$Jw>O?2i0Cila#v|twub21@e{S2v}8Z13( zNDrTXZVgris|qYm<0NU(tAPouG!QF4ZNpZPkX~{tVf8xY690JqY1NVdiTtW+NqyRP zZ&;T0ikb8V{wxmFhlLTQ&?OP7 z;(z*<+?J2~z*6asSe7h`$8~Se(@t(#%?BGLVs$p``;CyvcT?7Y!{tIPva$LxCQ&4W z6v#F*);|RXvI%qnoOY&i4S*EL&h%hP3O zLsrFZhv&Hu5tF$Lx!8(hs&?!Kx5&L(fdu}UI5d*wn~A`nPUhG&Rv z2#ixiJdhSF-K2tpVL=)5UkXRuPAFrEW}7mW=uAmtVQ&pGE-&az6@#-(Te^n*lrH^m@X-ftVcwO_#7{WI)5v(?>uC9GG{lcGXYJ~Q8q zbMFl7;t+kV;|;KkBW2!P_o%Czhw&Q(nXlxK9ak&6r5t_KH8#1Mr-*0}2h8R9XNkr zto5-b7P_auqTJb(TJlmJ9xreA=6d=d)CVbYP-r4$hDn5|TIhB>SReMfh&OVLkMk-T zYf%$taLF0OqYF?V{+6Xkn>iX@TuqQ?&cN6UjC9YF&%q{Ut3zv{U2)~$>-3;Dp)*(? zg*$mu8^i=-e#acaj*T$pNowo{xiGEk$%DusaQiS!KjJH96XZ-hXv+jk%ard#fu=@Q z$AM)YWvE^{%tDfK%nD49=PI|wYu}lYVbB#a7wtN^Nml@CE@{Gv7+jo{_V?I*jkdLD zJE|jfdrmVbkfS>rN*+`#l%ZUi5_bMS<>=MBDNlpiSb_tAF|Zy`K7kcp@|d?yaTmB^ zo?(vg;B$vxS|SszusORgDg-*Uitzdi{dUV+glA~R8V(?`3GZIl^egW{a919!j#>f` znL1o_^-b`}xnU0+~KIFLQ)$Q6#ym%)(GYC`^XM*{g zv3AM5$+TtDRs%`2TyR^$(hqE7Y1b&`Jd6dS6B#hDVbJlUXcG3y*439D8MrK!2D~6gn>UD4Imctb z+IvAt0iaW73Iq$K?4}H`7wq6YkTMm`tcktXgK0lKPmh=>h+l}Y+pDtvHnG>uqBA)l zAH6BV4F}v$(o$8Gfo*PB>IuaY1*^*`OTx4|hM8jZ?B6HY;F6p4{`OcZZ(us-RVwDx zUzJrCQlp@mz1ZFiSZ*$yX3c_#h9J;yBE$2g%xjmGF4ca z&yL`nGVs!Zxsh^j6i%$a*I3ZD2SoNT`{D%mU=LKaEwbN(_J5%i-6Va?@*>=3(dQy` zOv%$_9lcy9+(t>qohkuU4r_P=R^6ME+wFu&LA9tw9RA?azGhjrVJKy&8=*qZT5Dr8g--d+S8zAyJ$1HlW3Olryt`yE zFIph~Z6oF&o64rw{>lgZISC6p^CBer9C5G6yq%?8tC+)7*d+ib^?fU!JRFxynRLEZ zj;?PwtS}Ao#9whV@KEmwQgM0TVP{hs>dg(1*DiMUOKHdQGIqa0`yZnHk9mtbPfoLx zo;^V6pKUJ!5#n`w2D&381#5#_t}AlTGEgDz$^;u;-vxDN?^#5!zN9ngytY@oTv!nc zp1Xn8uR$1Z;7vY`-<*?DfPHB;x|GUi_fI9@I9SVRv1)qETbNU_8{5U|(>Du84qP#7 z*l9Y$SgA&wGbj>R1YeT9vYjZuC@|{rajTL0f%N@>3$DFU=`lSPl=Iv;EjuGjBa$Gw zHD-;%YOE@<-!7-Mn`0WuO3oWuL6tB2cpPw~Nvuj|KM@))ixuDK`9;jGMe2d)7gHin zS<>k@!x;!TJEc#HdL#RF(`|4W+H88d4V%zlh(7#{q2d0OQX9*FW^`^_<3r$kabWAB z$9BONo5}*(%kx zOXi-yM_cmB3>inPpI~)duvZykJ@^^aWzQ=eQ&STUa}2uT@lV&WoRzkUoE`rR0)`=l zFT%f|LA9fCw>`enm$p7W^E@U7RNBtsh{_-7vVz3DtB*y#*~(L9+x9*wn8VjWw|Q~q zKFsj1Yl>;}%MG3=PY`$g$_mnyhuV&~O~u~)968$0b2!Jkd;2MtAP#ZDYw9hmK_+M$ zb3pxyYC&|CuAbtiG8HZjj?MZJBFbt`ryf+c1dXFuC z0*ZQhBzNBd*}s6K_G}(|Z_9NDV162#y%WSNe|FTDDhx)K!c(mMJh@h87@8(^YdK$&d*^WQe8Z53 z(|@MRJ$Lk-&ii74MPIs80WsOFZ(NX23oR-?As+*aq6b?~62@fSVmM-_*cb1RzZ)`5$agEiL`-E9s7{GM2?(KNPgK1(+c*|-FKoy}X(D_b#etO|YR z(BGZ)0Ntfv-7R4GHoXp?l5g#*={S1{u-QzxCGng*oWr~@X-5f~RA14b8~B+pLKvr4 zfgL|7I>jlak9>D4=(i(cqYf7#318!OSR=^`xxvI!bBlS??`xxWeg?+|>MxaIdH1U~#1tHu zB{QMR?EGRmQ_l4p6YXJ{o(hh-7Tdm>TAX380TZZZyVkqHNzjUn*_|cb?T? zt;d2s-?B#Mc>T-gvBmQZx(y_cfkXZO~{N zT6rP7SD6g~n9QJ)8F*8uHxTLCAZ{l1Y&?6v)BOJZ)=R-pY=Y=&1}jE7fQ>USS}xP#exo57uND0i*rEk@$;nLvRB@u~s^dwRf?G?_enN@$t* zbL%JO=rV(3Ju8#GqUpeE3l_Wu1lN9Y{D4uaUe`g>zlj$1ER$6S6@{m1!~V|bYkhZA z%CvrDRTkHuajMU8;&RZ&itnC~iYLW4DVkP<$}>#&(`UO>!n)Po;Mt(SY8Yb`AS9lt znbX^i?Oe9r_o=?})IHKHoQGKXsps_SE{hwrg?6dMI|^+$CeC&z@*LuF+P`7LfZ*yr+KN8B4{Nzv<`A(wyR@!|gw{zB6Ha ziwPAYh)oJ(nlqSknu(8g9N&1hu0$vFK$W#mp%>X~AU1ay+EKWcFdif{% z#4!4aoVVJ;ULmkQf!ke2}3hqxLK>eq|-d7Ly7-J9zMpT`?dxo6HdfJA|t)?qPEVBDv z{y_b?4^|YA4%WW0VZd8C(ZgQzRI5(I^)=Ub`Y#MHc@nv0w-DaJAqsbEHDWG8Ia6ju zo-iyr*sq((gEwCC&^TYBWt4_@|81?=B-?#P6NMff(*^re zYqvDuO`K@`mjm_Jd;mW_tP`3$cS?R$jR1ZN09$YO%_iBqh5ftzSpMQQtxKFU=FYmP zeY^jph+g<4>YO;U^O>-NFLn~-RqlHvnZl2yd2A{Yc1G@Ga$d+Q&(f^tnPf+Z7serIU};17+2DU_f4Z z@GaPFut27d?!YiD+QP@)T=77cR9~MK@bd~pY%X(h%L={{OIb8IQmf-!xmZkm8A0Ga zQSWONI17_ru5wpHg3jI@i9D+_Y|pCqVuHJNdHUauTD=R$JcD2K_liQisqG$(sm=k9;L* z!L?*4B~ql7uioSX$zWJ?;q-SWXRFhz2Jt4%fOHA=Bwf|RzhwqdXGr78y$J)LR7&3T zE1WWz*>GPWKZ0%|@%6=fyx)5rzUpI;bCj>3RKzNG_1w$fIFCZ&UR0(7S?g}`&Pg$M zf`SLsz8wK82Vyj7;RyKmY{a8G{2BHG%w!^T|Njr!h9TO2LaP^_f22Q1=l$QiU84ao zHe_#{S6;qrC6w~7{y(hs-?-j?lbOfgH^E=XcSgnwW*eEz{_Z<_Px$?ny*JR5%f>l)FnDQ543{x%ZCiu33$Wg!pQFfT_}?5Q|_VSlIbLC`dpoMXL}9 zHfd9&47Mo(7D231gb+kjFxZHS4-m~7WurTH&doVX2KI5sU4v(sJ1@T9eCIKPjsqSr z)C01LsCxk=72-vXmX}CQD#BD;Cthymh&~=f$Q8nn0J<}ZrusBy4PvRNE}+1ceuj8u z0mW5k8fmgeLnTbWHGwfKA3@PdZxhn|PypR&^p?weGftrtCbjF#+zk_5BJh7;0`#Wr zgDpM_;Ax{jO##IrT`Oz;MvfwGfV$zD#c2xckpcXC6oou4ML~ezCc2EtnsQTB4tWNg z?4bkf;hG7IMfhgNI(FV5Gs4|*GyMTIY0$B=_*mso9Ityq$m^S>15>-?0(zQ<8Qy<_TjHE33(?_M8oaM zyc;NxzRVK@DL6RJnX%U^xW0Gpg(lXp(!uK1v0YgHjs^ZXSQ|m#lV7ip7{`C_J2TxPmfw%h$|%acrYHt)Re^PB%O&&=~a zhS(%I#+V>J-vjIib^<+s%ludY7y^C(P8nmqn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i z!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^0;J00)_^G=au=8Yo;1b`CV&@#=jIBo zjN^JNVfYSs)+kDdGe7`1&8!?MQYKS?DuHZf3iogk_%#9E|5S zWeHrmAo>P;ejX7mwq#*}W25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+ zX$F_KMdb6sRz!~7KkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&Q->Na@Xb&u5Q3`3DGf+a8O5x7c#7+R+EAYl@R5us)CIw z7sT@_y~Ao@uL#&^LIh&QceqiT^+lb0YbFZt_SHOtWA%mgPEKVNvVgCsXy{5+zl*X8 zCJe)Q@y>wH^>l4;h1l^Y*9%-23TSmE>q5nI@?mt%n;Sj4Qq`Z+ib)a*a^cJc%E9^J zB;4s+K@rARbcBLT5P=@r;IVnBMKvT*)ew*R;&8vu%?Z&S>s?8?)3*YawM0P4!q$Kv zMmKh3lgE~&w&v%wVzH3Oe=jeNT=n@Y6J6TdHWTjXfX~-=1A1Bw`EW8rn}MqeI34nh zexFeA?&C3B2(E?0{drE@DA2pu(A#ElY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEU zghdWK)^71EWCP(@(=c4kfH1Y(4iugD4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ! z)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup8IOUx8D4uh&(=O zx-7$a;U><*5L^!%xRlw)vAbh;sdlR||& ze}8_8%)c2Fwy=F&H|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}JeGuMZk~LPi7{cidvUGB zAJ4LVeNV%XO>LTrklB#^-;8nb;}6l;1oW&WS=Mz*Az!4cqqQzbOSFq`$Q%PfD7srM zpKgP-D_0XPTRX*hAqeq0TDkJ;5HB1%$3Np)99#16c{ zJImlNL(npL!W|Gr_kxl1GVmF5&^$^YherS7+~q$p zt}{a=*RiD2Ikv6o=IM1kgc7zqpaZ;OB)P!1zz*i3{U()Dq#jG)egvK}@uFLa`oyWZ zf~=MV)|yJn`M^$N%ul5);JuQvaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$ tNIIaX+dr)NRWXcxoR{>fqI{SF_dm1Ylv~=3YHI)h002ovPDHLkV1g(pWS;;4 literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ef06e7edb86cdfe0d15b4b0d98334a86163658 GIT binary patch literal 1716 zcmds$`#;kQ7{|XelZftyR5~xW7?MLxS4^|Hw3&P7^y)@A9Fj{Xm1~_CIV^XZ%SLBn zA;!r`GqGHg=7>xrB{?psZQs88ZaedDoagm^KF{a*>G|dJWRSe^I$DNW008I^+;Kjt z>9p3GNR^I;v>5_`+91i(*G;u5|L+Bu6M=(afLjtkya#yZ175|z$pU~>2#^Z_pCZ7o z1c6UNcv2B3?; zX%qdxCXQpdKRz=#b*q0P%b&o)5ZrNZt7$fiETSK_VaY=mb4GK`#~0K#~9^ zcY!`#Af+4h?UMR-gMKOmpuYeN5P*RKF!(tb`)oe0j2BH1l?=>y#S5pMqkx6i{*=V9JF%>N8`ewGhRE(|WohnD59R^$_36{4>S zDFlPC5|k?;SPsDo87!B{6*7eqmMdU|QZ84>6)Kd9wNfh90=y=TFQay-0__>=<4pk& zYDjgIhL-jQ9o>z32K)BgAH+HxamL{ZL~ozu)Qqe@a`FpH=oQRA8=L-m-1dam(Ix2V z?du;LdMO+ooBelr^_y4{|44tmgH^2hSzPFd;U^!1p>6d|o)(-01z{i&Kj@)z-yfWQ)V#3Uo!_U}q3u`(fOs`_f^ueFii1xBNUB z6MecwJN$CqV&vhc+)b(p4NzGGEgwWNs z@*lUV6LaduZH)4_g!cE<2G6#+hJrWd5(|p1Z;YJ7ifVHv+n49btR}dq?HHDjl{m$T z!jLZcGkb&XS2OG~u%&R$(X+Z`CWec%QKt>NGYvd5g20)PU(dOn^7%@6kQb}C(%=vr z{?RP(z~C9DPnL{q^@pVw@|Vx~@3v!9dCaBtbh2EdtoNHm4kGxp>i#ct)7p|$QJs+U z-a3qtcPvhihub?wnJqEt>zC@)2suY?%-96cYCm$Q8R%-8$PZYsx3~QOLMDf(piXMm zB=<63yQk1AdOz#-qsEDX>>c)EES%$owHKue;?B3)8aRd}m~_)>SL3h2(9X;|+2#7X z+#2)NpD%qJvCQ0a-uzZLmz*ms+l*N}w)3LRQ*6>|Ub-fyptY(keUxw+)jfwF5K{L9 z|Cl_w=`!l_o><384d&?)$6Nh(GAm=4p_;{qVn#hI8lqewW7~wUlyBM-4Z|)cZr?Rh z=xZ&Ol>4(CU85ea(CZ^aO@2N18K>ftl8>2MqetAR53_JA>Fal`^)1Y--Am~UDa4th zKfCYpcXky$XSFDWBMIl(q=Mxj$iMBX=|j9P)^fDmF(5(5$|?Cx}DKEJa&XZP%OyE`*GvvYQ4PV&!g2|L^Q z?YG}tx;sY@GzMmsY`7r$P+F_YLz)(e}% zyakqFB<6|x9R#TdoP{R$>o7y(-`$$p0NxJ6?2B8tH)4^yF(WhqGZlM3=9Ibs$%U1w zWzcss*_c0=v_+^bfb`kBFsI`d;ElwiU%frgRB%qBjn@!0U2zZehBn|{%uNIKBA7n= zzE`nnwTP85{g;8AkYxA68>#muXa!G>xH22D1I*SiD~7C?7Za+9y7j1SHiuSkKK*^O zsZ==KO(Ua#?YUpXl{ViynyT#Hzk=}5X$e04O@fsMQjb}EMuPWFO0e&8(2N(29$@Vd zn1h8Yd>6z(*p^E{c(L0Lg=wVdupg!z@WG;E0k|4a%s7Up5C0c)55XVK*|x9RQeZ1J@1v9MX;>n34(i>=YE@Iur`0Vah(inE3VUFZNqf~tSz{1fz3Fsn_x4F>o(Yo;kpqvBe-sbwH(*Y zu$JOl0b83zu$JMvy<#oH^Wl>aWL*?aDwnS0iEAwC?DK@aT)GHRLhnz2WCvf3Ba;o=aY7 z2{Asu5MEjGOY4O#Ggz@@J;q*0`kd2n8I3BeNuMmYZf{}pg=jTdTCrIIYuW~luKecn z+E-pHY%ohj@uS0%^ z&(OxwPFPD$+#~`H?fMvi9geVLci(`K?Kj|w{rZ9JgthFHV+=6vMbK~0)Ea<&WY-NC zy-PnZft_k2tfeQ*SuC=nUj4H%SQ&Y$gbH4#2sT0cU0SdFs=*W*4hKGpuR1{)mV;Qf5pw4? zfiQgy0w3fC*w&Bj#{&=7033qFR*<*61B4f9K%CQvxEn&bsWJ{&winp;FP!KBj=(P6 z4Z_n4L7cS;ao2)ax?Tm|I1pH|uLpDSRVghkA_UtFFuZ0b2#>!8;>-_0ELjQSD-DRd z4im;599VHDZYtnWZGAB25W-e(2VrzEh|etsv2YoP#VbIZ{aFkwPrzJ#JvCvA*mXS& z`}Q^v9(W4GiSs}#s7BaN!WA2bniM$0J(#;MR>uIJ^uvgD3GS^%*ikdW6-!VFUU?JV zZc2)4cMsX@j z5HQ^e3BUzOdm}yC-xA%SY``k$rbfk z;CHqifhU*jfGM@DkYCecD9vl*qr58l6x<8URB=&%{!Cu3RO*MrKZ4VO}V6R0a zZw3Eg^0iKWM1dcTYZ0>N899=r6?+adUiBKPciJw}L$=1f4cs^bio&cr9baLF>6#BM z(F}EXe-`F=f_@`A7+Q&|QaZ??Txp_dB#lg!NH=t3$G8&06MFhwR=Iu*Im0s_b2B@| znW>X}sy~m#EW)&6E&!*0%}8UAS)wjt+A(io#wGI@Z2S+Ms1Cxl%YVE800007ip7{`C_J2TxPmfw%h$|%acrYHt)Re^PB%O&&=~a zhS(%I#+V>J-vjIib^<+s%ludY7y^C(P8nmqn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i z!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^0;J00)_^G=au=8Yo;1b`CV&@#=jIBo zjN^JNVfYSs)+kDdGe7`1&8!?MQYKS?DuHZf3iogk_%#9E|5S zWeHrmAo>P;ejX7mwq#*}W25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+ zX$F_KMdb6sRz!~7KkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&Q->Na@Xb&u5Q3`3DGf+a8O5x7c#7+R+EAYl@R5us)CIw z7sT@_y~Ao@uL#&^LIh&QceqiT^+lb0YbFZt_SHOtWA%mgPEKVNvVgCsXy{5+zl*X8 zCJe)Q@y>wH^>l4;h1l^Y*9%-23TSmE>q5nI@?mt%n;Sj4Qq`Z+ib)a*a^cJc%E9^J zB;4s+K@rARbcBLT5P=@r;IVnBMKvT*)ew*R;&8vu%?Z&S>s?8?)3*YawM0P4!q$Kv zMmKh3lgE~&w&v%wVzH3Oe=jeNT=n@Y6J6TdHWTjXfX~-=1A1Bw`EW8rn}MqeI34nh zexFeA?&C3B2(E?0{drE@DA2pu(A#ElY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEU zghdWK)^71EWCP(@(=c4kfH1Y(4iugD4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ! z)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup8IOUx8D4uh&(=O zx-7$a;U><*5L^!%xRlw)vAbh;sdlR||& ze}8_8%)c2Fwy=F&H|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}JeGuMZk~LPi7{cidvUGB zAJ4LVeNV%XO>LTrklB#^-;8nb;}6l;1oW&WS=Mz*Az!4cqqQzbOSFq`$Q%PfD7srM zpKgP-D_0XPTRX*hAqeq0TDkJ;5HB1%$3Np)99#16c{ zJImlNL(npL!W|Gr_kxl1GVmF5&^$^YherS7+~q$p zt}{a=*RiD2Ikv6o=IM1kgc7zqpaZ;OB)P!1zz*i3{U()Dq#jG)egvK}@uFLa`oyWZ zf~=MV)|yJn`M^$N%ul5);JuQvaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$ tNIIaX+dr)NRWXcxoR{>fqI{SF_dm1Ylv~=3YHI)h002ovPDHLkV1g(pWS;;4 literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f9ed8f5cee1c98386d13b17e89f719e83555b2 GIT binary patch literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..75b2d164a5a98e212cca15ea7bf2ab5de5108680 GIT binary patch literal 3831 zcmVjJBgitF5mAp-i>4+KS_oR{|13AP->1TD4=w)g|)JHOx|a2Wk1Va z!k)vP$UcQ#mdj%wNQoaJ!w>jv_6&JPyutpQps?s5dmDQ>`%?Bvj>o<%kYG!YW6H-z zu`g$@mp`;qDR!51QaS}|ZToSuAGcJ7$2HF0z`ln4t!#Yg46>;vGG9N9{V@9z#}6v* zfP?}r6b{*-C*)(S>NECI_E~{QYzN5SXRmVnP<=gzP+_Sp(Aza_hKlZ{C1D&l*(7IKXxQC1Z9#6wx}YrGcn~g%;icdw>T0Rf^w0{ z$_wn1J+C0@!jCV<%Go5LA45e{5gY9PvZp8uM$=1}XDI+9m7!A95L>q>>oe0$nC->i zeexUIvq%Uk<-$>DiDb?!In)lAmtuMWxvWlk`2>4lNuhSsjAf2*2tjT`y;@d}($o)S zn(+W&hJ1p0xy@oxP%AM15->wPLp{H!k)BdBD$toBpJh+crWdsNV)qsHaqLg2_s|Ih z`8E9z{E3sA!}5aKu?T!#enD(wLw?IT?k-yWVHZ8Akz4k5(TZJN^zZgm&zM28sfTD2BYJ|Fde3Xzh;;S` z=GXTnY4Xc)8nYoz6&vF;P7{xRF-{|2Xs5>a5)@BrnQ}I(_x7Cgpx#5&Td^4Q9_FnQ zX5so*;#8-J8#c$OlA&JyPp$LKUhC~-e~Ij!L%uSMu!-VZG7Hx-L{m2DVR2i=GR(_% zCVD!4N`I)&Q5S`?P&fQZ=4#Dgt_v2-DzkT}K(9gF0L(owe-Id$Rc2qZVLqI_M_DyO z9@LC#U28_LU{;wGZ&))}0R2P4MhajKCd^K#D+JJ&JIXZ_p#@+7J9A&P<0kdRujtQ_ zOy>3=C$kgi6$0pW06KaLz!21oOryKM3ZUOWqppndxfH}QpgjEJ`j7Tzn5bk6K&@RA?vl##y z$?V~1E(!wB5rH`>3nc&@)|#<1dN2cMzzm=PGhQ|Yppne(C-Vlt450IXc`J4R0W@I7 zd1e5uW6juvO%ni(WX7BsKx3MLngO7rHO;^R5I~0^nE^9^E_eYLgiR9&KnJ)pBbfno zSVnW$0R+&6jOOsZ82}nJ126+c|%svPo;TeUku<2G7%?$oft zyaO;tVo}(W)VsTUhq^XmFi#2z%-W9a{7mXn{uzivYQ_d6b7VJG{77naW(vHt-uhnY zVN#d!JTqVh(7r-lhtXVU6o})aZbDt_;&wJVGl2FKYFBFpU-#9U)z#(A%=IVnqytR$SY-sO( z($oNE09{D^@OuYPz&w~?9>Fl5`g9u&ecFGhqX=^#fmR=we0CJw+5xna*@oHnkahk+ z9aWeE3v|An+O5%?4fA&$Fgu~H_YmqR!yIU!bFCk4!#pAj%(lI(A5n)n@Id#M)O9Yx zJU9oKy{sRAIV3=5>(s8n{8ryJ!;ho}%pn6hZKTKbqk=&m=f*UnK$zW3YQP*)pw$O* zIfLA^!-bmBl6%d_n$#tP8Zd_(XdA*z*WH|E_yILwjtI~;jK#v-6jMl^?<%Y%`gvpwv&cFb$||^v4D&V=aNy?NGo620jL3VZnA%s zH~I|qPzB~e(;p;b^gJr7Ure#7?8%F0m4vzzPy^^(q4q1OdthF}Fi*RmVZN1OwTsAP zn9CZP`FazX3^kG(KodIZ=Kty8DLTy--UKfa1$6XugS zk%6v$Kmxt6U!YMx0JQ)0qX*{CXwZZk$vEROidEc7=J-1;peNat!vS<3P-FT5po>iE z!l3R+<`#x|+_hw!HjQGV=8!q|76y8L7N8gP3$%0kfush|u0uU^?dKBaeRSBUpOZ0c z62;D&Mdn2}N}xHRFTRI?zRv=>=AjHgH}`2k4WK=#AHB)UFrR-J87GgX*x5fL^W2#d z=(%K8-oZfMO=i{aWRDg=FX}UubM4eotRDcn;OR#{3q=*?3mE3_oJ-~prjhxh%PgQT zyn)Qozaq0@o&|LEgS{Ind4Swsr;b`u185hZPOBLL<`d2%^Yp1?oL)=jnLi;Zo0ZDliTtQ^b5SmfIMe{T==zZkbvn$KTQGlbG8w}s@M3TZnde;1Am46P3juKb zl9GU&3F=q`>j!`?SyH#r@O59%@aMX^rx}Nxe<>NqpUp5=lX1ojGDIR*-D^SDuvCKF z?3$xG(gVUsBERef_YjPFl^rU9EtD{pt z0CXwpN7BN3!8>hajGaTVk-wl=9rxmfWtIhC{mheHgStLi^+Nz12a?4r(fz)?3A%at zMlvQmL<2-R)-@G1wJ0^zQK%mR=r4d{Y3fHp){nWXUL#|CqXl(+v+qDh>FkF9`eWrW zfr^D%LNfOcTNvtx0JXR35J0~Jpi2#P3Q&80w+nqNfc}&G0A~*)lGHKv=^FE+b(37|)zL;KLF>oiGfb(?&1 zV3XRu!Sw>@quKiab%g6jun#oZ%!>V#A%+lNc?q>6+VvyAn=kf_6z^(TZUa4Eelh{{ zqFX-#dY(EV@7l$NE&kv9u9BR8&Ojd#ZGJ6l8_BW}^r?DIS_rU2(XaGOK z225E@kH5Opf+CgD^{y29jD4gHbGf{1MD6ggQ&%>UG4WyPh5q_tb`{@_34B?xfSO*| zZv8!)q;^o-bz`MuxXk*G^}(6)ACb@=Lfs`Hxoh>`Y0NE8QRQ!*p|SH@{r8=%RKd4p z+#Ty^-0kb=-H-O`nAA3_6>2z(D=~Tbs(n8LHxD0`R0_ATFqp-SdY3(bZ3;VUM?J=O zKCNsxsgt@|&nKMC=*+ZqmLHhX1KHbAJs{nGVMs6~TiF%Q)P@>!koa$%oS zjXa=!5>P`vC-a}ln!uH1ooeI&v?=?v7?1n~P(wZ~0>xWxd_Aw;+}9#eULM7M8&E?Y zC-ZLhi3RoM92SXUb-5i-Lmt5_rfjE{6y^+24`y$1lywLyHO!)Boa7438K4#iLe?rh z2O~YGSgFUBH?og*6=r9rme=peP~ah`(8Zt7V)j5!V0KPFf_mebo3z95U8(up$-+EA^9dTRLq>Yl)YMBuch9%=e5B`Vnb>o zt03=kq;k2TgGe4|lGne&zJa~h(UGutjP_zr?a7~#b)@15XNA>Dj(m=gg2Q5V4-$)D|Q9}R#002ovPDHLkV1o7DH3k3x literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4df70d39da7941ef3f6dcb7f06a192d8dcb308d GIT binary patch literal 1888 zcmV-m2cP(fP)x~L`~4d)Rspd&<9kFh{hn*KP1LP0~$;u(LfAu zp%fx&qLBcRHx$G|3q(bv@+b;o0*D|jwD-Q9uQR(l*ST}s+uPgQ-MeFwZ#GS?b332? z&Tk$&_miXn3IGq)AmQ)3sisq{raD4(k*bHvpCe-TdWq^NRTEVM)i9xbgQ&ccnUVx* zEY%vS%gDcSg=!tuIK8$Th2_((_h^+7;R|G{n06&O2#6%LK`a}n?h_fL18btz<@lFG za}xS}u?#DBMB> zw^b($1Z)`9G?eP95EKi&$eOy@K%h;ryrR3la%;>|o*>CgB(s>dDcNOXg}CK9SPmD? zmr-s{0wRmxUnbDrYfRvnZ@d z6johZ2sMX{YkGSKWd}m|@V7`Degt-43=2M?+jR%8{(H$&MLLmS;-|JxnX2pnz;el1jsvqQz}pGSF<`mqEXRQ5sC4#BbwnB_4` zc5bFE-Gb#JV3tox9fp-vVEN{(tOCpRse`S+@)?%pz+zVJXSooTrNCUg`R6`hxwb{) zC@{O6MKY8tfZ5@!yy=p5Y|#+myRL=^{tc(6YgAnkg3I(Cd!r5l;|;l-MQ8B`;*SCE z{u)uP^C$lOPM z5d~UhKhRRmvv{LIa^|oavk1$QiEApSrP@~Jjbg`<*dW4TO?4qG%a%sTPUFz(QtW5( zM)lA+5)0TvH~aBaOAs|}?u2FO;yc-CZ1gNM1dAxJ?%m?YsGR`}-xk2*dxC}r5j$d* zE!#Vtbo69h>V4V`BL%_&$} z+oJAo@jQ^Tk`;%xw-4G>hhb&)B?##U+(6Fi7nno`C<|#PVA%$Y{}N-?(Gc$1%tr4Pc}}hm~yY#fTOe!@v9s-ik$dX~|ygArPhByaXn8 zpI^FUjNWMsTFKTP3X7m?UK)3m zp6rI^_zxRYrx6_QmhoWoDR`fp4R7gu6;gdO)!KexaoO2D88F9x#TM1(9Bn7g;|?|o z)~$n&Lh#hCP6_LOPD>a)NmhW})LADx2kq=X7}7wYRj-0?dXr&bHaRWCfSqvzFa=sn z-8^gSyn-RmH=BZ{AJZ~!8n5621GbUJV7Qvs%JNv&$%Q17s_X%s-41vAPfIR>;x0Wlqr5?09S>x#%Qkt>?(&XjFRY}*L6BeQ3 z<6XEBh^S7>AbwGm@XP{RkeEKj6@_o%oV?hDuUpUJ+r#JZO?!IUc;r0R?>mi)*ZpQ) z#((dn=A#i_&EQn|hd)N$#A*fjBFuiHcYvo?@y1 z5|fV=a^a~d!c-%ZbMNqkMKiSzM{Yq=7_c&1H!mXk60Uv32dV;vMg&-kQ)Q{+PFtwc zj|-uQ;b^gts??J*9VxxOro}W~Q9j4Em|zSRv)(WSO9$F$s=Ydu%Q+5DOid~lwk&we zY%W(Z@ofdwPHncEZzZgmqS|!gTj3wQq9rxQy+^eNYKr1mj&?tm@wkO*9@UtnRMG>c aR{jt9+;fr}hV%pg00001^@s67{VYS000c7NklQEG_j zup^)eW&WUIApqy$=APz8jE@awGp)!bsTjDbrJO`$x^ZR^dr;>)LW>{ zs70vpsD38v)19rI=GNk1b(0?Js9~rjsQsu*K;@SD40RB-3^gKU-MYC7G!Bw{fZsqp zih4iIi;Hr_xZ033Iu{sQxLS=}yBXgLMn40d++>aQ0#%8D1EbGZp7+ z5=mK?t31BkVYbGOxE9`i748x`YgCMwL$qMsChbSGSE1`p{nSmadR zcQ#R)(?!~dmtD0+D2!K zR9%!Xp1oOJzm(vbLvT^$IKp@+W2=-}qTzTgVtQ!#Y7Gxz}stUIm<1;oBQ^Sh2X{F4ibaOOx;5ZGSNK z0maF^@(UtV$=p6DXLgRURwF95C=|U8?osGhgOED*b z7woJ_PWXBD>V-NjQAm{~T%sjyJ{5tn2f{G%?J!KRSrrGvQ1(^`YLA5B!~eycY(e5_ z*%aa{at13SxC(=7JT7$IQF~R3sy`Nn%EMv!$-8ZEAryB*yB1k&stni)=)8-ODo41g zkJu~roIgAih94tb=YsL%iH5@^b~kU9M-=aqgXIrbtxMpFy5mekFm#edF9z7RQ6V}R zBIhbXs~pMzt0VWy1Fi$^fh+1xxLDoK09&5&MJl(q#THjPm(0=z2H2Yfm^a&E)V+a5 zbi>08u;bJsDRUKR9(INSc7XyuWv(JsD+BB*0hS)FO&l&7MdViuur@-<-EHw>kHRGY zqoT}3fDv2-m{NhBG8X}+rgOEZ;amh*DqN?jEfQdqxdj08`Sr=C-KmT)qU1 z+9Cl)a1mgXxhQiHVB}l`m;-RpmKy?0*|yl?FXvJkFxuu!fKlcmz$kN(a}i*saM3nr z0!;a~_%Xqy24IxA2rz<+08=B-Q|2PT)O4;EaxP^6qixOv7-cRh?*T?zZU`{nIM-at zTKYWr9rJ=tppQ9I#Z#mLgINVB!pO-^FOcvFw6NhV0gztuO?g ztoA*C-52Q-Z-P#xB4HAY3KQVd%dz1S4PA3vHp0aa=zAO?FCt zC_GaTyVBg2F!bBr3U@Zy2iJgIAt>1sf$JWA9kh{;L+P*HfUBX1Zy{4MgNbDfBV_ly z!y#+753arsZUt@366jIC0klaC@ckuk!qu=pAyf7&QmiBUT^L1&tOHzsK)4n|pmrVT zs2($4=?s~VejTFHbFdDOwG;_58LkIj1Fh@{glkO#F1>a==ymJS$z;gdedT1zPx4Kj ztjS`y_C}%af-RtpehdQDt3a<=W5C4$)9W@QAse;WUry$WYmr51ml9lkeunUrE`-3e zmq1SgSOPNEE-Mf+AGJ$g0M;3@w!$Ej;hMh=v=I+Lpz^n%Pg^MgwyqOkNyu2c^of)C z1~ALor3}}+RiF*K4+4{(1%1j3pif1>sv0r^mTZ?5Jd-It!tfPfiG_p$AY*Vfak%FG z4z#;wLtw&E&?}w+eKG^=#jF7HQzr8rV0mY<1YAJ_uGz~$E13p?F^fPSzXSn$8UcI$ z8er9{5w5iv0qf8%70zV71T1IBB1N}R5Kp%NO0=5wJalZt8;xYp;b{1K) zHY>2wW-`Sl{=NpR%iu3(u6l&)rc%%cSA#aV7WCowfbFR4wcc{LQZv~o1u_`}EJA3>ki`?9CKYTA!rhO)if*zRdd}Kn zEPfYbhoVE~!FI_2YbC5qAj1kq;xP6%J8+?2PAs?`V3}nyFVD#sV3+uP`pi}{$l9U^ zSz}_M9f7RgnnRhaoIJgT8us!1aB&4!*vYF07Hp&}L zCRlop0oK4DL@ISz{2_BPlezc;xj2|I z23RlDNpi9LgTG_#(w%cMaS)%N`e>~1&a3<{Xy}>?WbF>OOLuO+j&hc^YohQ$4F&ze z+hwnro1puQjnKm;vFG~o>`kCeUIlkA-2tI?WBKCFLMBY=J{hpSsQ=PDtU$=duS_hq zHpymHt^uuV1q@uc4bFb{MdG*|VoW@15Osrqt2@8ll0qO=j*uOXn{M0UJX#SUztui9FN4)K3{9!y8PC-AHHvpVTU;x|-7P+taAtyglk#rjlH2 z5Gq8ik}BPaGiM{#Woyg;*&N9R2{J0V+WGB69cEtH7F?U~Kbi6ksi*`CFXsi931q7Y zGO82?whBhN%w1iDetv%~wM*Y;E^)@Vl?VDj-f*RX>{;o_=$fU!&KAXbuadYZ46Zbg z&6jMF=49$uL^73y;;N5jaHYv)BTyfh&`qVLYn?`o6BCA_z-0niZz=qPG!vonK3MW_ zo$V96zM!+kJRs{P-5-rQVse0VBH*n6A58)4uc&gfHMa{gIhV2fGf{st>E8sKyP-$8zp~wJX^A*@DI&-;8>gANXZj zU)R+Y)PB?=)a|Kj>8NXEu^S_h^7R`~Q&7*Kn!xyvzVv&^>?^iu;S~R2e-2fJx-oUb cX)(b1KSk$MOV07*qoM6N<$f&6$jw%VRuvdN2+38CZWny1cRtlsl+0_KtW)EU14Ei(F!UtWuj4IK+3{sK@>rh zs1Z;=(DD&U6+tlyL?UnHVN^&g6QhFi2#HS+*qz;(>63G(`|jRtW|nz$Pv7qTovP!^ zP_jES{mr@O-02w%!^a?^1ZP!_KmQiz0L~jZ=W@Qt`8wzOoclQsAS<5YdH;a(4bGLE zk8s}1If(PSIgVi!XE!5kA?~z*sobvNyohr;=Q_@h2@$6Flyej3J)D-6YfheRGl`HEcPk|~huT_2-U?PfL=4BPV)f1o!%rQ!NMt_MYw-5bUSwQ9Z&zC>u zOrl~UJglJNa%f50Ok}?WB{on`Ci`p^Y!xBA?m@rcJXLxtrE0FhRF3d*ir>yzO|BD$ z3V}HpFcCh6bTzY}Nt_(W%QYd3NG)jJ4<`F<1Od) zfQblTdC&h2lCz`>y?>|9o2CdvC8qZeIZt%jN;B7Hdn2l*k4M4MFEtq`q_#5?}c$b$pf_3y{Y!cRDafZBEj-*OD|gz#PBDeu3QoueOesLzB+O zxjf2wvf6Wwz>@AiOo2mO4=TkAV+g~%_n&R;)l#!cBxjuoD$aS-`IIJv7cdX%2{WT7 zOm%5rs(wqyPE^k5SIpUZ!&Lq4<~%{*>_Hu$2|~Xa;iX*tz8~G6O3uFOS?+)tWtdi| zV2b#;zRN!m@H&jd=!$7YY6_}|=!IU@=SjvGDFtL;aCtw06U;-v^0%k0FOyESt z1Wv$={b_H&8FiRV?MrzoHWd>%v6KTRU;-v^Miiz+@q`(BoT!+<37CKhoKb)|8!+RG z6BQFU^@fRW;s8!mOf2QViKQGk0TVER6EG1`#;Nm39Do^PoT!+<37AD!%oJe86(=et zZ~|sLzU>V-qYiU6V8$0GmU7_K8|Fd0B?+9Un1BhKAz#V~Fk^`mJtlCX#{^8^M8!me z8Yg;8-~>!e<-iG;h*0B1kBKm}hItVGY6WnjVpgnTTAC$rqQ^v)4KvOtpY|sIj@WYg zyw##ZZ5AC2IKNC;^hwg9BPk0wLStlmBr;E|$5GoAo$&Ui_;S9WY62n3)i49|T%C#i017z3J=$RF|KyZWnci*@lW4 z=AKhNN6+m`Q!V3Ye68|8y@%=am>YD0nG99M)NWc20%)gwO!96j7muR}Fr&54SxKP2 zP30S~lt=a*qDlbu3+Av57=9v&vr<6g0&`!8E2fq>I|EJGKs}t|{h7+KT@)LfIV-3K zK)r_fr2?}FFyn*MYoLC>oV-J~eavL2ho4a4^r{E-8m2hi>~hA?_vIG4a*KT;2eyl1 zh_hUvUJpNCFwBvRq5BI*srSle>c6%n`#VNsyC|MGa{(P&08p=C9+WUw9Hl<1o9T4M zdD=_C0F7#o8A_bRR?sFNmU0R6tW`ElnF8p53IdHo#S9(JoZCz}fHwJ6F<&?qrpVqE zte|m%89JQD+XwaPU#%#lVs-@-OL);|MdfINd6!XwP2h(eyafTUsoRkA%&@fe?9m@jw-v(yTTiV2(*fthQH9}SqmsRPVnwwbV$1E(_lkmo&S zF-truCU914_$jpqjr(>Ha4HkM4YMT>m~NosUu&UZ>zirfHo%N6PPs9^_o$WqPA0#5 z%tG>qFCL+b*0s?sZ;Sht0nE7Kl>OVXy=gjWxxK;OJ3yGd7-pZf7JYNcZo2*1SF`u6 zHJyRRxGw9mDlOiXqVMsNe#WX`fC`vrtjSQ%KmLcl(lC>ZOQzG^%iql2w-f_K@r?OE zwCICifM#L-HJyc7Gm>Ern?+Sk3&|Khmu4(~3qa$(m6Ub^U0E5RHq49za|XklN#?kP zl;EstdW?(_4D>kwjWy2f!LM)y?F94kyU3`W!6+AyId-89v}sXJpuic^NLL7GJItl~ zsiuB98AI-(#Mnm|=A-R6&2fwJ0JVSY#Q>&3$zFh|@;#%0qeF=j5Ajq@4i0tIIW z&}sk$&fGwoJpe&u-JeGLi^r?dO`m=y(QO{@h zQqAC7$rvz&5+mo3IqE?h=a~6m>%r5Quapvzq;{y~p zJpyXOBgD9VrW7@#p6l7O?o3feml(DtSL>D^R) zZUY%T2b0-vBAFN7VB;M88!~HuOXi4KcI6aRQ&h|XQ0A?m%j2=l1f0cGP}h(oVfJ`N zz#PpmFC*ieab)zJK<4?^k=g%OjPnkANzbAbmGZHoVRk*mTfm75s_cWVa`l*f$B@xu z5E*?&@seIo#*Y~1rBm!7sF9~~u6Wrj5oICUOuz}CS)jdNIznfzCA(stJ(7$c^e5wN z?lt>eYgbA!kvAR7zYSD&*r1$b|(@;9dcZ^67R0 zXAXJKa|5Sdmj!g578Nwt6d$sXuc&MWezA0Whd`94$h{{?1IwXP4)Tx4obDK%xoFZ_Z zjjHJ_P@R_e5blG@yEjnaJb`l;s%Lb2&=8$&Ct-fV`E^4CUs)=jTk!I}2d&n!f@)bm z@ z_4Dc86+3l2*p|~;o-Sb~oXb_RuLmoifDU^&Te$*FevycC0*nE3Xws8gsWp|Rj2>SM zns)qcYj?^2sd8?N!_w~4v+f-HCF|a$TNZDoNl$I1Uq87euoNgKb6&r26TNrfkUa@o zfdiFA@p{K&mH3b8i!lcoz)V{n8Q@g(vR4ns4r6w;K z>1~ecQR0-<^J|Ndg5fvVUM9g;lbu-){#ghGw(fg>L zh)T5Ljb%lWE;V9L!;Cqk>AV1(rULYF07ZBJbGb9qbSoLAd;in9{)95YqX$J43-dY7YU*k~vrM25 zxh5_IqO0LYZW%oxQ5HOzmk4x{atE*vipUk}sh88$b2tn?!ujEHn`tQLe&vo}nMb&{ zio`xzZ&GG6&ZyN3jnaQy#iVqXE9VT(3tWY$n-)uWDQ|tc{`?fq2F`oQ{;d3aWPg4Hp-(iE{ry>MIPWL> iW8Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_officecore/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_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/syncfusion_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_officecore/example/ios/Runner/Base.lproj/Main.storyboard b/packages/syncfusion_officecore/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_officecore/example/ios/Runner/Info.plist b/packages/syncfusion_officecore/example/ios/Runner/Info.plist new file mode 100644 index 000000000..44da86d86 --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + xlsio_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_officecore/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_officecore/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/syncfusion_officecore/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/syncfusion_officecore/example/main.dart b/packages/syncfusion_officecore/example/lib/main.dart similarity index 98% rename from packages/syncfusion_officecore/example/main.dart rename to packages/syncfusion_officecore/example/lib/main.dart index 0efb334e1..2abbc4470 100644 --- a/packages/syncfusion_officecore/example/main.dart +++ b/packages/syncfusion_officecore/example/lib/main.dart @@ -61,7 +61,7 @@ class _CreateExcelState extends State { final Workbook workbook = Workbook(); //Accessing via index final Worksheet sheet = workbook.worksheets[0]; - sheet.showGridLines = false; + sheet.showGridlines = false; // Enable calculation for worksheet. sheet.enableSheetCalculations(); @@ -214,7 +214,7 @@ class _CreateExcelState extends State { range9.cellStyle.vAlign = VAlignType.center; //Save and launch the excel. - final List bytes = workbook.saveStream(); + final List bytes = workbook.saveAsStream(); //Dispose the document. workbook.dispose(); diff --git a/packages/syncfusion_officecore/example/officecore_example.iml b/packages/syncfusion_officecore/example/officecore_example.iml new file mode 100644 index 000000000..e5c837191 --- /dev/null +++ b/packages/syncfusion_officecore/example/officecore_example.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/syncfusion_officecore/example/pubspec.yaml b/packages/syncfusion_officecore/example/pubspec.yaml new file mode 100644 index 000000000..af268bab7 --- /dev/null +++ b/packages/syncfusion_officecore/example/pubspec.yaml @@ -0,0 +1,19 @@ +name: officecore_example +description: Demo for creating a Excel file using syncfusion_flutter_officecore package. + +dependencies: + flutter: + sdk: flutter + path_provider: ^1.6.7 + open_file: ^3.0.1 + syncfusion_flutter_xlsio: + git: + url: https://buildautomation:Coolcomp299@gitlab.syncfusion.com/essential-studio/flutter-xlsio + path: flutter_xlsio/syncfusion_flutter_xlsio + branch: release/18.4.0.1 + ref: release/18.4.0.1 + + +# The following section is specific to Flutter. +flutter: + uses-material-design: true