From 64e733647f669c95de43b8465b817673769ea9f1 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 3 Jan 2023 15:43:29 -0500 Subject: [PATCH 01/18] Roll Plugins from b202b3db98dc to e85f8ac1502d (3 revisions) (#117875) * 035d85e62 Roll Flutter from d2127ad344e8 to 120058fd3ded (15 revisions) (flutter/plugins#6896) * 80532e0ba Roll Flutter from 120058fd3ded to 0196e6050b75 (3 revisions) (flutter/plugins#6901) * e85f8ac15 Roll Flutter from 0196e6050b75 to b938dc13df32 (7 revisions) (flutter/plugins#6908) --- bin/internal/flutter_plugins.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/flutter_plugins.version b/bin/internal/flutter_plugins.version index 6176642f261ce..6188351624d2d 100644 --- a/bin/internal/flutter_plugins.version +++ b/bin/internal/flutter_plugins.version @@ -1 +1 @@ -b202b3db98dcd61f888b76e8224f9b0296f5ba44 +e85f8ac1502db556e03953794ad0aa9149ddb02a From fe8dcf6631ea214fff589d933f61db10072867cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20S=20Guerrero?= Date: Tue, 3 Jan 2023 14:44:53 -0600 Subject: [PATCH 02/18] [flutter_tools] timeline_test.dart flaky (#116667) * contains name instead of remove last * fix expect * remove and expect on elements * delete unused code --- .../test/integration.shard/timeline_test.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/flutter_tools/test/integration.shard/timeline_test.dart b/packages/flutter_tools/test/integration.shard/timeline_test.dart index 0d6fecde986e7..d2344c00bb4c6 100644 --- a/packages/flutter_tools/test/integration.shard/timeline_test.dart +++ b/packages/flutter_tools/test/integration.shard/timeline_test.dart @@ -96,7 +96,15 @@ void main() { // The downloaded part of the timeline may contain an end event whose // corresponding begin event happened before the start of the timeline. if (stack.isNotEmpty) { - expect(stack.removeLast(), name); + bool pass = false; + while (stack.isNotEmpty) { + final String value = stack.removeLast(); + if (value == name) { + pass = true; + break; + } + } + expect(pass, true); } } } From f1905593b7c1eb1f27864568eab037aef364e2d1 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 3 Jan 2023 15:47:58 -0500 Subject: [PATCH 03/18] 7e51aef0a Roll Skia from fde37f5986fd to 809e328ed55c (1 revision) (flutter/engine#38596) (#117874) --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 02206e87e01f0..a3825e26ecad6 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -472e34cbbcd461c748973e7e735558ab200d4f5e +7e51aef0a1be6d97d476ec196b03cc6224527780 From ccfd14b05f4d6e64871ba5309517a7fd26f2968d Mon Sep 17 00:00:00 2001 From: Darren Austin Date: Tue, 3 Jan 2023 12:48:00 -0800 Subject: [PATCH 04/18] Updated to tokens v0.150. (#117350) * Updated to tokens v0.150. * Updated with a reverted list_tile.dart. --- dev/tools/gen_defaults/data/badge.json | 2 +- dev/tools/gen_defaults/data/banner.json | 2 +- dev/tools/gen_defaults/data/bottom_app_bar.json | 2 +- dev/tools/gen_defaults/data/button_elevated.json | 2 +- dev/tools/gen_defaults/data/button_filled.json | 2 +- .../gen_defaults/data/button_filled_tonal.json | 2 +- dev/tools/gen_defaults/data/button_outlined.json | 2 +- dev/tools/gen_defaults/data/button_text.json | 2 +- dev/tools/gen_defaults/data/card_elevated.json | 2 +- dev/tools/gen_defaults/data/card_filled.json | 2 +- dev/tools/gen_defaults/data/card_outlined.json | 10 +++++----- dev/tools/gen_defaults/data/checkbox.json | 2 +- dev/tools/gen_defaults/data/chip_assist.json | 2 +- dev/tools/gen_defaults/data/chip_filter.json | 4 ++-- dev/tools/gen_defaults/data/chip_input.json | 2 +- dev/tools/gen_defaults/data/chip_suggestion.json | 12 ++++++------ dev/tools/gen_defaults/data/color_dark.json | 2 +- dev/tools/gen_defaults/data/color_light.json | 2 +- .../gen_defaults/data/date_picker_docked.json | 2 +- .../data/date_picker_input_modal.json | 16 ++++++++++++++++ .../gen_defaults/data/date_picker_modal.json | 2 +- dev/tools/gen_defaults/data/dialog.json | 2 +- .../gen_defaults/data/dialog_fullscreen.json | 2 +- dev/tools/gen_defaults/data/divider.json | 2 +- dev/tools/gen_defaults/data/elevation.json | 2 +- .../gen_defaults/data/fab_extended_primary.json | 2 +- .../gen_defaults/data/fab_large_primary.json | 2 +- dev/tools/gen_defaults/data/fab_primary.json | 2 +- .../gen_defaults/data/fab_small_primary.json | 2 +- dev/tools/gen_defaults/data/icon_button.json | 2 +- .../gen_defaults/data/icon_button_filled.json | 2 +- .../data/icon_button_filled_tonal.json | 2 +- .../gen_defaults/data/icon_button_outlined.json | 2 +- dev/tools/gen_defaults/data/list.json | 2 +- dev/tools/gen_defaults/data/menu.json | 2 +- dev/tools/gen_defaults/data/motion.json | 2 +- dev/tools/gen_defaults/data/navigation_bar.json | 2 +- .../gen_defaults/data/navigation_drawer.json | 2 +- dev/tools/gen_defaults/data/navigation_rail.json | 2 +- .../data/navigation_tab_primary.json | 2 +- dev/tools/gen_defaults/data/palette.json | 2 +- .../data/progress_indicator_circular.json | 2 +- .../data/progress_indicator_linear.json | 2 +- dev/tools/gen_defaults/data/radio_button.json | 2 +- .../data/segmented_button_outlined.json | 2 +- dev/tools/gen_defaults/data/shape.json | 2 +- dev/tools/gen_defaults/data/sheet_bottom.json | 2 +- dev/tools/gen_defaults/data/slider.json | 2 +- dev/tools/gen_defaults/data/snackbar.json | 2 +- dev/tools/gen_defaults/data/state.json | 2 +- dev/tools/gen_defaults/data/switch.json | 2 +- .../gen_defaults/data/text_field_filled.json | 4 ++-- .../gen_defaults/data/text_field_outlined.json | 2 +- dev/tools/gen_defaults/data/text_style.json | 4 +++- dev/tools/gen_defaults/data/time_picker.json | 4 ++-- .../gen_defaults/data/top_app_bar_large.json | 2 +- .../gen_defaults/data/top_app_bar_medium.json | 2 +- .../gen_defaults/data/top_app_bar_small.json | 2 +- dev/tools/gen_defaults/data/typeface.json | 2 +- .../flutter/lib/src/material/action_chip.dart | 2 +- packages/flutter/lib/src/material/app_bar.dart | 2 +- packages/flutter/lib/src/material/badge.dart | 2 +- packages/flutter/lib/src/material/banner.dart | 2 +- .../flutter/lib/src/material/bottom_app_bar.dart | 2 +- .../flutter/lib/src/material/bottom_sheet.dart | 2 +- packages/flutter/lib/src/material/card.dart | 2 +- packages/flutter/lib/src/material/checkbox.dart | 2 +- packages/flutter/lib/src/material/chip.dart | 2 +- .../flutter/lib/src/material/choice_chip.dart | 4 ++-- packages/flutter/lib/src/material/dialog.dart | 4 ++-- packages/flutter/lib/src/material/divider.dart | 2 +- packages/flutter/lib/src/material/drawer.dart | 2 +- .../lib/src/material/elevated_button.dart | 2 +- .../lib/src/material/elevation_overlay.dart | 2 +- .../flutter/lib/src/material/filled_button.dart | 4 ++-- .../flutter/lib/src/material/filter_chip.dart | 4 ++-- .../lib/src/material/floating_action_button.dart | 2 +- .../flutter/lib/src/material/icon_button.dart | 2 +- .../flutter/lib/src/material/input_chip.dart | 2 +- .../lib/src/material/input_decorator.dart | 2 +- .../flutter/lib/src/material/menu_anchor.dart | 2 +- .../flutter/lib/src/material/navigation_bar.dart | 2 +- .../lib/src/material/navigation_drawer.dart | 2 +- .../lib/src/material/navigation_rail.dart | 2 +- .../lib/src/material/outlined_button.dart | 2 +- .../flutter/lib/src/material/popup_menu.dart | 2 +- .../lib/src/material/progress_indicator.dart | 2 +- packages/flutter/lib/src/material/radio.dart | 2 +- .../lib/src/material/segmented_button.dart | 2 +- packages/flutter/lib/src/material/slider.dart | 2 +- packages/flutter/lib/src/material/snack_bar.dart | 2 +- packages/flutter/lib/src/material/switch.dart | 2 +- packages/flutter/lib/src/material/tabs.dart | 2 +- .../flutter/lib/src/material/text_button.dart | 2 +- .../flutter/lib/src/material/text_field.dart | 2 +- .../flutter/lib/src/material/theme_data.dart | 2 +- .../flutter/lib/src/material/time_picker.dart | 6 +++--- .../flutter/lib/src/material/typography.dart | 2 +- 98 files changed, 133 insertions(+), 115 deletions(-) create mode 100644 dev/tools/gen_defaults/data/date_picker_input_modal.json diff --git a/dev/tools/gen_defaults/data/badge.json b/dev/tools/gen_defaults/data/badge.json index 1b339472a39e6..dca20c1a13089 100644 --- a/dev/tools/gen_defaults/data/badge.json +++ b/dev/tools/gen_defaults/data/badge.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.badge.color": "error", "md.comp.badge.large.color": "error", diff --git a/dev/tools/gen_defaults/data/banner.json b/dev/tools/gen_defaults/data/banner.json index 0f942e18d2524..c74ebc84927b2 100644 --- a/dev/tools/gen_defaults/data/banner.json +++ b/dev/tools/gen_defaults/data/banner.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.banner.container.color": "surface", "md.comp.banner.container.elevation": "md.sys.elevation.level1", diff --git a/dev/tools/gen_defaults/data/bottom_app_bar.json b/dev/tools/gen_defaults/data/bottom_app_bar.json index 065a2c584edd3..eacd39de5c088 100644 --- a/dev/tools/gen_defaults/data/bottom_app_bar.json +++ b/dev/tools/gen_defaults/data/bottom_app_bar.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.bottom-app-bar.container.color": "surface", "md.comp.bottom-app-bar.container.elevation": "md.sys.elevation.level2", diff --git a/dev/tools/gen_defaults/data/button_elevated.json b/dev/tools/gen_defaults/data/button_elevated.json index eba01c49d1b90..0aa524e82930e 100644 --- a/dev/tools/gen_defaults/data/button_elevated.json +++ b/dev/tools/gen_defaults/data/button_elevated.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.elevated-button.container.color": "surface", "md.comp.elevated-button.container.elevation": "md.sys.elevation.level1", diff --git a/dev/tools/gen_defaults/data/button_filled.json b/dev/tools/gen_defaults/data/button_filled.json index ade79ff6c0c0f..d574a34b8870e 100644 --- a/dev/tools/gen_defaults/data/button_filled.json +++ b/dev/tools/gen_defaults/data/button_filled.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.filled-button.container.color": "primary", "md.comp.filled-button.container.elevation": "md.sys.elevation.level0", diff --git a/dev/tools/gen_defaults/data/button_filled_tonal.json b/dev/tools/gen_defaults/data/button_filled_tonal.json index e38530e52d73b..5f94c5a8478d2 100644 --- a/dev/tools/gen_defaults/data/button_filled_tonal.json +++ b/dev/tools/gen_defaults/data/button_filled_tonal.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.filled-tonal-button.container.color": "secondaryContainer", "md.comp.filled-tonal-button.container.elevation": "md.sys.elevation.level0", diff --git a/dev/tools/gen_defaults/data/button_outlined.json b/dev/tools/gen_defaults/data/button_outlined.json index f78703c76f163..937db4a820007 100644 --- a/dev/tools/gen_defaults/data/button_outlined.json +++ b/dev/tools/gen_defaults/data/button_outlined.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.outlined-button.container.height": 40.0, "md.comp.outlined-button.container.shape": "md.sys.shape.corner.full", diff --git a/dev/tools/gen_defaults/data/button_text.json b/dev/tools/gen_defaults/data/button_text.json index 25bd0fcee5392..2faf767288e94 100644 --- a/dev/tools/gen_defaults/data/button_text.json +++ b/dev/tools/gen_defaults/data/button_text.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.text-button.container.height": 40.0, "md.comp.text-button.container.shape": "md.sys.shape.corner.full", diff --git a/dev/tools/gen_defaults/data/card_elevated.json b/dev/tools/gen_defaults/data/card_elevated.json index 2d11c1aae9d6e..e22c357c95efd 100644 --- a/dev/tools/gen_defaults/data/card_elevated.json +++ b/dev/tools/gen_defaults/data/card_elevated.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.elevated-card.container.color": "surface", "md.comp.elevated-card.container.elevation": "md.sys.elevation.level1", diff --git a/dev/tools/gen_defaults/data/card_filled.json b/dev/tools/gen_defaults/data/card_filled.json index ea9bc471f4456..eb504a144a90f 100644 --- a/dev/tools/gen_defaults/data/card_filled.json +++ b/dev/tools/gen_defaults/data/card_filled.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.filled-card.container.color": "surfaceVariant", "md.comp.filled-card.container.elevation": "md.sys.elevation.level0", diff --git a/dev/tools/gen_defaults/data/card_outlined.json b/dev/tools/gen_defaults/data/card_outlined.json index a0c5f9187b9c9..309cd46f0ba6f 100644 --- a/dev/tools/gen_defaults/data/card_outlined.json +++ b/dev/tools/gen_defaults/data/card_outlined.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.outlined-card.container.color": "surface", "md.comp.outlined-card.container.elevation": "md.sys.elevation.level0", @@ -10,7 +10,7 @@ "md.comp.outlined-card.disabled.outline.color": "outline", "md.comp.outlined-card.disabled.outline.opacity": 0.12, "md.comp.outlined-card.dragged.container.elevation": "md.sys.elevation.level3", - "md.comp.outlined-card.dragged.outline.color": "outline", + "md.comp.outlined-card.dragged.outline.color": "outlineVariant", "md.comp.outlined-card.dragged.state-layer.color": "onSurface", "md.comp.outlined-card.dragged.state-layer.opacity": "md.sys.state.dragged.state-layer-opacity", "md.comp.outlined-card.focus.container.elevation": "md.sys.elevation.level0", @@ -18,15 +18,15 @@ "md.comp.outlined-card.focus.state-layer.color": "onSurface", "md.comp.outlined-card.focus.state-layer.opacity": "md.sys.state.focus.state-layer-opacity", "md.comp.outlined-card.hover.container.elevation": "md.sys.elevation.level1", - "md.comp.outlined-card.hover.outline.color": "outline", + "md.comp.outlined-card.hover.outline.color": "outlineVariant", "md.comp.outlined-card.hover.state-layer.color": "onSurface", "md.comp.outlined-card.hover.state-layer.opacity": "md.sys.state.hover.state-layer-opacity", "md.comp.outlined-card.icon.color": "primary", "md.comp.outlined-card.icon.size": 24.0, - "md.comp.outlined-card.outline.color": "outline", + "md.comp.outlined-card.outline.color": "outlineVariant", "md.comp.outlined-card.outline.width": 1.0, "md.comp.outlined-card.pressed.container.elevation": "md.sys.elevation.level0", - "md.comp.outlined-card.pressed.outline.color": "outline", + "md.comp.outlined-card.pressed.outline.color": "outlineVariant", "md.comp.outlined-card.pressed.state-layer.color": "onSurface", "md.comp.outlined-card.pressed.state-layer.opacity": "md.sys.state.pressed.state-layer-opacity" } diff --git a/dev/tools/gen_defaults/data/checkbox.json b/dev/tools/gen_defaults/data/checkbox.json index de4c0a7bada72..02bb59867d79a 100644 --- a/dev/tools/gen_defaults/data/checkbox.json +++ b/dev/tools/gen_defaults/data/checkbox.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.checkbox.container.height": 18.0, "md.comp.checkbox.container.width": 18.0, diff --git a/dev/tools/gen_defaults/data/chip_assist.json b/dev/tools/gen_defaults/data/chip_assist.json index 2de0f21b52b52..9554d2fb84cba 100644 --- a/dev/tools/gen_defaults/data/chip_assist.json +++ b/dev/tools/gen_defaults/data/chip_assist.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.assist-chip.container.height": 32.0, "md.comp.assist-chip.container.shape": "md.sys.shape.corner.small", diff --git a/dev/tools/gen_defaults/data/chip_filter.json b/dev/tools/gen_defaults/data/chip_filter.json index d09df0b77b9d1..5a6b54f88139f 100644 --- a/dev/tools/gen_defaults/data/chip_filter.json +++ b/dev/tools/gen_defaults/data/chip_filter.json @@ -1,14 +1,14 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.filter-chip.container.height": 32.0, - "md.comp.filter-chip.container.shadow-color": "shadow", "md.comp.filter-chip.container.shape": "md.sys.shape.corner.small", "md.comp.filter-chip.container.surface-tint-layer.color": "surfaceTint", "md.comp.filter-chip.disabled.label-text.color": "onSurface", "md.comp.filter-chip.disabled.label-text.opacity": 0.38, "md.comp.filter-chip.dragged.container.elevation": "md.sys.elevation.level4", "md.comp.filter-chip.elevated.container.elevation": "md.sys.elevation.level1", + "md.comp.filter-chip.elevated.container.shadow-color": "shadow", "md.comp.filter-chip.elevated.disabled.container.color": "onSurface", "md.comp.filter-chip.elevated.disabled.container.elevation": "md.sys.elevation.level0", "md.comp.filter-chip.elevated.disabled.container.opacity": 0.12, diff --git a/dev/tools/gen_defaults/data/chip_input.json b/dev/tools/gen_defaults/data/chip_input.json index 14528cc1cd4ef..3e93abe9116d2 100644 --- a/dev/tools/gen_defaults/data/chip_input.json +++ b/dev/tools/gen_defaults/data/chip_input.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.input-chip.container.elevation": "md.sys.elevation.level0", "md.comp.input-chip.container.height": 32.0, diff --git a/dev/tools/gen_defaults/data/chip_suggestion.json b/dev/tools/gen_defaults/data/chip_suggestion.json index 5e1dbc83c6179..0aa0d14846970 100644 --- a/dev/tools/gen_defaults/data/chip_suggestion.json +++ b/dev/tools/gen_defaults/data/chip_suggestion.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.suggestion-chip.container.height": 32.0, "md.comp.suggestion-chip.container.shape": "md.sys.shape.corner.small", @@ -38,10 +38,10 @@ "md.comp.suggestion-chip.pressed.state-layer.opacity": "md.sys.state.pressed.state-layer-opacity", "md.comp.suggestion-chip.with-leading-icon.disabled.leading-icon.color": "onSurface", "md.comp.suggestion-chip.with-leading-icon.disabled.leading-icon.opacity": 0.38, - "md.comp.suggestion-chip.with-leading-icon.dragged.leading-icon.color": "onSurfaceVariant", - "md.comp.suggestion-chip.with-leading-icon.focus.leading-icon.color": "onSurfaceVariant", - "md.comp.suggestion-chip.with-leading-icon.hover.leading-icon.color": "onSurfaceVariant", - "md.comp.suggestion-chip.with-leading-icon.leading-icon.color": "onSurfaceVariant", + "md.comp.suggestion-chip.with-leading-icon.dragged.leading-icon.color": "primary", + "md.comp.suggestion-chip.with-leading-icon.focus.leading-icon.color": "primary", + "md.comp.suggestion-chip.with-leading-icon.hover.leading-icon.color": "primary", + "md.comp.suggestion-chip.with-leading-icon.leading-icon.color": "primary", "md.comp.suggestion-chip.with-leading-icon.leading-icon.size": 18.0, - "md.comp.suggestion-chip.with-leading-icon.pressed.leading-icon.color": "onSurfaceVariant" + "md.comp.suggestion-chip.with-leading-icon.pressed.leading-icon.color": "primary" } diff --git a/dev/tools/gen_defaults/data/color_dark.json b/dev/tools/gen_defaults/data/color_dark.json index 11286ad792dae..14e7ada0901b6 100644 --- a/dev/tools/gen_defaults/data/color_dark.json +++ b/dev/tools/gen_defaults/data/color_dark.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.sys.color.background": "md.ref.palette.neutral10", "md.sys.color.error": "md.ref.palette.error80", diff --git a/dev/tools/gen_defaults/data/color_light.json b/dev/tools/gen_defaults/data/color_light.json index 046f615c636f5..eb7e8eb5ccc0f 100644 --- a/dev/tools/gen_defaults/data/color_light.json +++ b/dev/tools/gen_defaults/data/color_light.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.sys.color.background": "md.ref.palette.neutral99", "md.sys.color.error": "md.ref.palette.error40", diff --git a/dev/tools/gen_defaults/data/date_picker_docked.json b/dev/tools/gen_defaults/data/date_picker_docked.json index 97f077c096389..a54a3021bc313 100644 --- a/dev/tools/gen_defaults/data/date_picker_docked.json +++ b/dev/tools/gen_defaults/data/date_picker_docked.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.date-picker.docked.container.color": "surface", "md.comp.date-picker.docked.container.elevation": "md.sys.elevation.level3", diff --git a/dev/tools/gen_defaults/data/date_picker_input_modal.json b/dev/tools/gen_defaults/data/date_picker_input_modal.json new file mode 100644 index 0000000000000..2e7bae63efb6d --- /dev/null +++ b/dev/tools/gen_defaults/data/date_picker_input_modal.json @@ -0,0 +1,16 @@ +{ + "version": "v0_150", + + "md.comp.date-input.modal.container.color": "surface", + "md.comp.date-input.modal.container.elevation": "md.sys.elevation.level3", + "md.comp.date-input.modal.container.height": 512.0, + "md.comp.date-input.modal.container.shape": "md.sys.shape.corner.extra-large", + "md.comp.date-input.modal.container.surface-tint-layer.color": "surfaceTint", + "md.comp.date-input.modal.container.width": 328.0, + "md.comp.date-input.modal.header.container.height": 120.0, + "md.comp.date-input.modal.header.container.width": 328.0, + "md.comp.date-input.modal.header.headline.color": "onSurfaceVariant", + "md.comp.date-input.modal.header.headline.text-style": "headlineLarge", + "md.comp.date-input.modal.header.supporting-text.color": "onSurfaceVariant", + "md.comp.date-input.modal.header.supporting-text.text-style": "labelMedium" +} diff --git a/dev/tools/gen_defaults/data/date_picker_modal.json b/dev/tools/gen_defaults/data/date_picker_modal.json index 9a2ea4a6b2d08..c29400a8395a1 100644 --- a/dev/tools/gen_defaults/data/date_picker_modal.json +++ b/dev/tools/gen_defaults/data/date_picker_modal.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.date-picker.modal.container.color": "surface", "md.comp.date-picker.modal.container.elevation": "md.sys.elevation.level3", diff --git a/dev/tools/gen_defaults/data/dialog.json b/dev/tools/gen_defaults/data/dialog.json index bcf6c94eaf932..988b4dbbbc66a 100644 --- a/dev/tools/gen_defaults/data/dialog.json +++ b/dev/tools/gen_defaults/data/dialog.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.dialog.action.focus.label-text.color": "primary", "md.comp.dialog.action.focus.state-layer.color": "primary", diff --git a/dev/tools/gen_defaults/data/dialog_fullscreen.json b/dev/tools/gen_defaults/data/dialog_fullscreen.json index 5d4d305516dd4..30d84108a6ce4 100644 --- a/dev/tools/gen_defaults/data/dialog_fullscreen.json +++ b/dev/tools/gen_defaults/data/dialog_fullscreen.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.full-screen-dialog.container.color": "surface", "md.comp.full-screen-dialog.container.elevation": "md.sys.elevation.level0", diff --git a/dev/tools/gen_defaults/data/divider.json b/dev/tools/gen_defaults/data/divider.json index 27c56b93d2e00..f56367924e765 100644 --- a/dev/tools/gen_defaults/data/divider.json +++ b/dev/tools/gen_defaults/data/divider.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.divider.color": "outlineVariant", "md.comp.divider.thickness": 1.0 diff --git a/dev/tools/gen_defaults/data/elevation.json b/dev/tools/gen_defaults/data/elevation.json index 11c9c7d50972d..9b55f955a3717 100644 --- a/dev/tools/gen_defaults/data/elevation.json +++ b/dev/tools/gen_defaults/data/elevation.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.sys.elevation.level0": 0.0, "md.sys.elevation.level1": 1.0, diff --git a/dev/tools/gen_defaults/data/fab_extended_primary.json b/dev/tools/gen_defaults/data/fab_extended_primary.json index a2038fc34feb9..4a81c01b1231f 100644 --- a/dev/tools/gen_defaults/data/fab_extended_primary.json +++ b/dev/tools/gen_defaults/data/fab_extended_primary.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.extended-fab.primary.container.color": "primaryContainer", "md.comp.extended-fab.primary.container.elevation": "md.sys.elevation.level3", diff --git a/dev/tools/gen_defaults/data/fab_large_primary.json b/dev/tools/gen_defaults/data/fab_large_primary.json index 6df0e38b59abb..4d66920545d1d 100644 --- a/dev/tools/gen_defaults/data/fab_large_primary.json +++ b/dev/tools/gen_defaults/data/fab_large_primary.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.fab.primary.large.container.color": "primaryContainer", "md.comp.fab.primary.large.container.elevation": "md.sys.elevation.level3", diff --git a/dev/tools/gen_defaults/data/fab_primary.json b/dev/tools/gen_defaults/data/fab_primary.json index f76f1965f77c0..a2bb47baa03ae 100644 --- a/dev/tools/gen_defaults/data/fab_primary.json +++ b/dev/tools/gen_defaults/data/fab_primary.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.fab.primary.container.color": "primaryContainer", "md.comp.fab.primary.container.elevation": "md.sys.elevation.level3", diff --git a/dev/tools/gen_defaults/data/fab_small_primary.json b/dev/tools/gen_defaults/data/fab_small_primary.json index b229979dff277..841ca591e9f6e 100644 --- a/dev/tools/gen_defaults/data/fab_small_primary.json +++ b/dev/tools/gen_defaults/data/fab_small_primary.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.fab.primary.small.container.color": "primaryContainer", "md.comp.fab.primary.small.container.elevation": "md.sys.elevation.level3", diff --git a/dev/tools/gen_defaults/data/icon_button.json b/dev/tools/gen_defaults/data/icon_button.json index 3e8ac2cf57398..26649d0adfc8d 100644 --- a/dev/tools/gen_defaults/data/icon_button.json +++ b/dev/tools/gen_defaults/data/icon_button.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.icon-button.disabled.icon.color": "onSurface", "md.comp.icon-button.disabled.icon.opacity": 0.38, diff --git a/dev/tools/gen_defaults/data/icon_button_filled.json b/dev/tools/gen_defaults/data/icon_button_filled.json index 15520cfd680fd..54d7593a4e7bb 100644 --- a/dev/tools/gen_defaults/data/icon_button_filled.json +++ b/dev/tools/gen_defaults/data/icon_button_filled.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.filled-icon-button.container.color": "primary", "md.comp.filled-icon-button.container.shape": "md.sys.shape.corner.full", diff --git a/dev/tools/gen_defaults/data/icon_button_filled_tonal.json b/dev/tools/gen_defaults/data/icon_button_filled_tonal.json index 29c1951ce309f..2ff2379da86d1 100644 --- a/dev/tools/gen_defaults/data/icon_button_filled_tonal.json +++ b/dev/tools/gen_defaults/data/icon_button_filled_tonal.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.filled-tonal-icon-button.container.color": "secondaryContainer", "md.comp.filled-tonal-icon-button.container.shape": "md.sys.shape.corner.full", diff --git a/dev/tools/gen_defaults/data/icon_button_outlined.json b/dev/tools/gen_defaults/data/icon_button_outlined.json index bb21480df8c6b..991a3078c1164 100644 --- a/dev/tools/gen_defaults/data/icon_button_outlined.json +++ b/dev/tools/gen_defaults/data/icon_button_outlined.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.outlined-icon-button.container.shape": "md.sys.shape.corner.full", "md.comp.outlined-icon-button.container.size": 40.0, diff --git a/dev/tools/gen_defaults/data/list.json b/dev/tools/gen_defaults/data/list.json index ca506622275e0..bc3be25bbcec6 100644 --- a/dev/tools/gen_defaults/data/list.json +++ b/dev/tools/gen_defaults/data/list.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.list.list-item.container.color": "surface", "md.comp.list.list-item.container.elevation": "md.sys.elevation.level0", diff --git a/dev/tools/gen_defaults/data/menu.json b/dev/tools/gen_defaults/data/menu.json index c630f144ff704..c1cdc425b2f9f 100644 --- a/dev/tools/gen_defaults/data/menu.json +++ b/dev/tools/gen_defaults/data/menu.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.menu.cascading-menu-indicator.icon.color": "onSurfaceVariant", "md.comp.menu.cascading-menu-indicator.icon.size": 24.0, diff --git a/dev/tools/gen_defaults/data/motion.json b/dev/tools/gen_defaults/data/motion.json index 0a3d93bebea00..7c1d282e236b2 100644 --- a/dev/tools/gen_defaults/data/motion.json +++ b/dev/tools/gen_defaults/data/motion.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.sys.motion.duration.extra-long1Ms": 700.0, "md.sys.motion.duration.extra-long2Ms": 800.0, diff --git a/dev/tools/gen_defaults/data/navigation_bar.json b/dev/tools/gen_defaults/data/navigation_bar.json index b0d12a8c060fc..f6f65b59649c3 100644 --- a/dev/tools/gen_defaults/data/navigation_bar.json +++ b/dev/tools/gen_defaults/data/navigation_bar.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.navigation-bar.active.focus.icon.color": "onSecondaryContainer", "md.comp.navigation-bar.active.focus.label-text.color": "onSurface", diff --git a/dev/tools/gen_defaults/data/navigation_drawer.json b/dev/tools/gen_defaults/data/navigation_drawer.json index 3ae63aab78823..ac64c6031e257 100644 --- a/dev/tools/gen_defaults/data/navigation_drawer.json +++ b/dev/tools/gen_defaults/data/navigation_drawer.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.navigation-drawer.active.focus.icon.color": "onSecondaryContainer", "md.comp.navigation-drawer.active.focus.label-text.color": "onSecondaryContainer", diff --git a/dev/tools/gen_defaults/data/navigation_rail.json b/dev/tools/gen_defaults/data/navigation_rail.json index 6e26ca5d3ba9a..810f9c44a7254 100644 --- a/dev/tools/gen_defaults/data/navigation_rail.json +++ b/dev/tools/gen_defaults/data/navigation_rail.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.navigation-rail.active.focus.icon.color": "onSecondaryContainer", "md.comp.navigation-rail.active.focus.label-text.color": "onSurface", diff --git a/dev/tools/gen_defaults/data/navigation_tab_primary.json b/dev/tools/gen_defaults/data/navigation_tab_primary.json index d001804289f5a..4c4000203fe69 100644 --- a/dev/tools/gen_defaults/data/navigation_tab_primary.json +++ b/dev/tools/gen_defaults/data/navigation_tab_primary.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.primary-navigation-tab.active.focus.state-layer.color": "primary", "md.comp.primary-navigation-tab.active.focus.state-layer.opacity": "md.sys.state.focus.state-layer-opacity", diff --git a/dev/tools/gen_defaults/data/palette.json b/dev/tools/gen_defaults/data/palette.json index d800aac4fd876..02ada690678e7 100644 --- a/dev/tools/gen_defaults/data/palette.json +++ b/dev/tools/gen_defaults/data/palette.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.ref.palette.black": "0xFF000000", "md.ref.palette.error0": "0xFF000000", diff --git a/dev/tools/gen_defaults/data/progress_indicator_circular.json b/dev/tools/gen_defaults/data/progress_indicator_circular.json index 3b2b00c281012..55026304b0245 100644 --- a/dev/tools/gen_defaults/data/progress_indicator_circular.json +++ b/dev/tools/gen_defaults/data/progress_indicator_circular.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.circular-progress-indicator.active-indicator.color": "primary", "md.comp.circular-progress-indicator.active-indicator.shape": "md.sys.shape.corner.none", diff --git a/dev/tools/gen_defaults/data/progress_indicator_linear.json b/dev/tools/gen_defaults/data/progress_indicator_linear.json index 4a9a0bcf357be..935dc314b4f0a 100644 --- a/dev/tools/gen_defaults/data/progress_indicator_linear.json +++ b/dev/tools/gen_defaults/data/progress_indicator_linear.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.linear-progress-indicator.active-indicator.color": "primary", "md.comp.linear-progress-indicator.active-indicator.height": 4.0, diff --git a/dev/tools/gen_defaults/data/radio_button.json b/dev/tools/gen_defaults/data/radio_button.json index fe52020698ebf..ab472bb2bcedd 100644 --- a/dev/tools/gen_defaults/data/radio_button.json +++ b/dev/tools/gen_defaults/data/radio_button.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.radio-button.disabled.selected.icon.color": "onSurface", "md.comp.radio-button.disabled.selected.icon.opacity": 0.38, diff --git a/dev/tools/gen_defaults/data/segmented_button_outlined.json b/dev/tools/gen_defaults/data/segmented_button_outlined.json index d56cfbc9a04af..9ae18897ca400 100644 --- a/dev/tools/gen_defaults/data/segmented_button_outlined.json +++ b/dev/tools/gen_defaults/data/segmented_button_outlined.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.outlined-segmented-button.container.height": 40.0, "md.comp.outlined-segmented-button.disabled.icon.color": "onSurface", diff --git a/dev/tools/gen_defaults/data/shape.json b/dev/tools/gen_defaults/data/shape.json index 8d02706b0a7c6..16fc796228371 100644 --- a/dev/tools/gen_defaults/data/shape.json +++ b/dev/tools/gen_defaults/data/shape.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.sys.shape.corner.extra-large": { "family": "SHAPE_FAMILY_ROUNDED_CORNERS", diff --git a/dev/tools/gen_defaults/data/sheet_bottom.json b/dev/tools/gen_defaults/data/sheet_bottom.json index 51c229862573c..2592f8c5c51a7 100644 --- a/dev/tools/gen_defaults/data/sheet_bottom.json +++ b/dev/tools/gen_defaults/data/sheet_bottom.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.sheet.bottom.docked.container.color": "surface", "md.comp.sheet.bottom.docked.container.shape": "md.sys.shape.corner.extra-large.top", diff --git a/dev/tools/gen_defaults/data/slider.json b/dev/tools/gen_defaults/data/slider.json index ec566ae54d388..4b503cb984b6c 100644 --- a/dev/tools/gen_defaults/data/slider.json +++ b/dev/tools/gen_defaults/data/slider.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.slider.active.track.color": "primary", "md.comp.slider.active.track.height": 4.0, diff --git a/dev/tools/gen_defaults/data/snackbar.json b/dev/tools/gen_defaults/data/snackbar.json index ee3b255089be1..a23de07c9fbd2 100644 --- a/dev/tools/gen_defaults/data/snackbar.json +++ b/dev/tools/gen_defaults/data/snackbar.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.snackbar.action.focus.label-text.color": "inversePrimary", "md.comp.snackbar.action.focus.state-layer.color": "inversePrimary", diff --git a/dev/tools/gen_defaults/data/state.json b/dev/tools/gen_defaults/data/state.json index 297c168732c97..7d5d307d37028 100644 --- a/dev/tools/gen_defaults/data/state.json +++ b/dev/tools/gen_defaults/data/state.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.sys.state.dragged.state-layer-opacity": 0.16, "md.sys.state.focus.state-layer-opacity": 0.12, diff --git a/dev/tools/gen_defaults/data/switch.json b/dev/tools/gen_defaults/data/switch.json index e7bf2dc6cf8dc..77e5628ddf6fe 100644 --- a/dev/tools/gen_defaults/data/switch.json +++ b/dev/tools/gen_defaults/data/switch.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.switch.disabled.selected.handle.color": "surface", "md.comp.switch.disabled.selected.handle.opacity": 1.0, diff --git a/dev/tools/gen_defaults/data/text_field_filled.json b/dev/tools/gen_defaults/data/text_field_filled.json index e9e99ca9c7d71..7ef457c255bb5 100644 --- a/dev/tools/gen_defaults/data/text_field_filled.json +++ b/dev/tools/gen_defaults/data/text_field_filled.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.filled-text-field.active-indicator.color": "onSurfaceVariant", "md.comp.filled-text-field.active-indicator.height": 1.0, @@ -66,7 +66,7 @@ "md.comp.filled-text-field.label-text.color": "onSurfaceVariant", "md.comp.filled-text-field.label-text.text-style": "bodyLarge", "md.comp.filled-text-field.leading-icon.color": "onSurfaceVariant", - "md.comp.filled-text-field.leading-icon.size": 20.0, + "md.comp.filled-text-field.leading-icon.size": 24.0, "md.comp.filled-text-field.supporting-text.color": "onSurfaceVariant", "md.comp.filled-text-field.supporting-text.text-style": "bodySmall", "md.comp.filled-text-field.trailing-icon.color": "onSurfaceVariant", diff --git a/dev/tools/gen_defaults/data/text_field_outlined.json b/dev/tools/gen_defaults/data/text_field_outlined.json index 7395b85979e7f..9becee372733e 100644 --- a/dev/tools/gen_defaults/data/text_field_outlined.json +++ b/dev/tools/gen_defaults/data/text_field_outlined.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.outlined-text-field.caret.color": "primary", "md.comp.outlined-text-field.container.shape": "md.sys.shape.corner.extra-small", diff --git a/dev/tools/gen_defaults/data/text_style.json b/dev/tools/gen_defaults/data/text_style.json index 6427bee094965..c0e1433b50764 100644 --- a/dev/tools/gen_defaults/data/text_style.json +++ b/dev/tools/gen_defaults/data/text_style.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.sys.typescale.body-large.font": "md.ref.typeface.plain", "md.sys.typescale.body-large.line-height": 24.0, @@ -51,11 +51,13 @@ "md.sys.typescale.label-large.size": 14.0, "md.sys.typescale.label-large.tracking": 0.1, "md.sys.typescale.label-large.weight": "md.ref.typeface.weight-medium", + "md.sys.typescale.label-large.weight.prominent": "md.ref.typeface.weight-bold", "md.sys.typescale.label-medium.font": "md.ref.typeface.plain", "md.sys.typescale.label-medium.line-height": 16.0, "md.sys.typescale.label-medium.size": 12.0, "md.sys.typescale.label-medium.tracking": 0.5, "md.sys.typescale.label-medium.weight": "md.ref.typeface.weight-medium", + "md.sys.typescale.label-medium.weight.prominent": "md.ref.typeface.weight-bold", "md.sys.typescale.label-small.font": "md.ref.typeface.plain", "md.sys.typescale.label-small.line-height": 16.0, "md.sys.typescale.label-small.size": 11.0, diff --git a/dev/tools/gen_defaults/data/time_picker.json b/dev/tools/gen_defaults/data/time_picker.json index 0de2dbd18df96..104c8bfa2cf1d 100644 --- a/dev/tools/gen_defaults/data/time_picker.json +++ b/dev/tools/gen_defaults/data/time_picker.json @@ -1,7 +1,7 @@ { - "version": "v0_143", + "version": "v0_150", - "md.comp.time-picker.clock-dial.color": "onSurfaceVariant", + "md.comp.time-picker.clock-dial.color": "surfaceVariant", "md.comp.time-picker.clock-dial.container.size": 256.0, "md.comp.time-picker.clock-dial.label-text.text-style": "bodyLarge", "md.comp.time-picker.clock-dial.selected.label-text.color": "onPrimary", diff --git a/dev/tools/gen_defaults/data/top_app_bar_large.json b/dev/tools/gen_defaults/data/top_app_bar_large.json index fcc841f4ab02d..58d3d43ae5a5a 100644 --- a/dev/tools/gen_defaults/data/top_app_bar_large.json +++ b/dev/tools/gen_defaults/data/top_app_bar_large.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.top-app-bar.large.container.color": "surface", "md.comp.top-app-bar.large.container.elevation": "md.sys.elevation.level0", diff --git a/dev/tools/gen_defaults/data/top_app_bar_medium.json b/dev/tools/gen_defaults/data/top_app_bar_medium.json index 4a206b0dfa302..82f48d5c5f2e9 100644 --- a/dev/tools/gen_defaults/data/top_app_bar_medium.json +++ b/dev/tools/gen_defaults/data/top_app_bar_medium.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.top-app-bar.medium.container.color": "surface", "md.comp.top-app-bar.medium.container.elevation": "md.sys.elevation.level0", diff --git a/dev/tools/gen_defaults/data/top_app_bar_small.json b/dev/tools/gen_defaults/data/top_app_bar_small.json index a02af86e0d2ef..fff3ef068245d 100644 --- a/dev/tools/gen_defaults/data/top_app_bar_small.json +++ b/dev/tools/gen_defaults/data/top_app_bar_small.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.comp.top-app-bar.small.container.color": "surface", "md.comp.top-app-bar.small.container.elevation": "md.sys.elevation.level0", diff --git a/dev/tools/gen_defaults/data/typeface.json b/dev/tools/gen_defaults/data/typeface.json index 825ab3ab58046..5164886a4251a 100644 --- a/dev/tools/gen_defaults/data/typeface.json +++ b/dev/tools/gen_defaults/data/typeface.json @@ -1,5 +1,5 @@ { - "version": "v0_143", + "version": "v0_150", "md.ref.typeface.brand": "Roboto", "md.ref.typeface.plain": "Roboto", diff --git a/packages/flutter/lib/src/material/action_chip.dart b/packages/flutter/lib/src/material/action_chip.dart index eaa09fd9f8c45..59766c50ef34c 100644 --- a/packages/flutter/lib/src/material/action_chip.dart +++ b/packages/flutter/lib/src/material/action_chip.dart @@ -178,7 +178,7 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _ActionChipDefaultsM3 extends ChipThemeData { const _ActionChipDefaultsM3(this.context, this.isEnabled) diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index ac25e22226034..a3ba8830e5bb5 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -2376,7 +2376,7 @@ class _AppBarDefaultsM2 extends AppBarTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _AppBarDefaultsM3 extends AppBarTheme { _AppBarDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/badge.dart b/packages/flutter/lib/src/material/badge.dart index c720b9d14f4b6..d91dc0c96e273 100644 --- a/packages/flutter/lib/src/material/badge.dart +++ b/packages/flutter/lib/src/material/badge.dart @@ -193,7 +193,7 @@ class Badge extends StatelessWidget { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _BadgeDefaultsM3 extends BadgeThemeData { _BadgeDefaultsM3(this.context) : super( diff --git a/packages/flutter/lib/src/material/banner.dart b/packages/flutter/lib/src/material/banner.dart index 245ba2ffbd7eb..9a17b4516cb67 100644 --- a/packages/flutter/lib/src/material/banner.dart +++ b/packages/flutter/lib/src/material/banner.dart @@ -453,7 +453,7 @@ class _BannerDefaultsM2 extends MaterialBannerThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _BannerDefaultsM3 extends MaterialBannerThemeData { const _BannerDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/bottom_app_bar.dart b/packages/flutter/lib/src/material/bottom_app_bar.dart index 761c53d97b42f..1e1af3dba1799 100644 --- a/packages/flutter/lib/src/material/bottom_app_bar.dart +++ b/packages/flutter/lib/src/material/bottom_app_bar.dart @@ -276,7 +276,7 @@ class _BottomAppBarDefaultsM2 extends BottomAppBarTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _BottomAppBarDefaultsM3 extends BottomAppBarTheme { const _BottomAppBarDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart index c0e19679af87c..1e56c36f68da0 100644 --- a/packages/flutter/lib/src/material/bottom_sheet.dart +++ b/packages/flutter/lib/src/material/bottom_sheet.dart @@ -1146,7 +1146,7 @@ PersistentBottomSheetController showBottomSheet({ // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _BottomSheetDefaultsM3 extends BottomSheetThemeData { const _BottomSheetDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/card.dart b/packages/flutter/lib/src/material/card.dart index d90fd0b94d2a5..ab8dc0fcf90d7 100644 --- a/packages/flutter/lib/src/material/card.dart +++ b/packages/flutter/lib/src/material/card.dart @@ -214,7 +214,7 @@ class _CardDefaultsM2 extends CardTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _CardDefaultsM3 extends CardTheme { const _CardDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/checkbox.dart b/packages/flutter/lib/src/material/checkbox.dart index f585a3d5f3d6c..03872f9f5da06 100644 --- a/packages/flutter/lib/src/material/checkbox.dart +++ b/packages/flutter/lib/src/material/checkbox.dart @@ -764,7 +764,7 @@ class _CheckboxDefaultsM2 extends CheckboxThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _CheckboxDefaultsM3 extends CheckboxThemeData { _CheckboxDefaultsM3(BuildContext context) diff --git a/packages/flutter/lib/src/material/chip.dart b/packages/flutter/lib/src/material/chip.dart index 30a2e97b985c5..7ccca1501ae11 100644 --- a/packages/flutter/lib/src/material/chip.dart +++ b/packages/flutter/lib/src/material/chip.dart @@ -2196,7 +2196,7 @@ bool _hitIsOnDeleteIcon({ // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _ChipDefaultsM3 extends ChipThemeData { const _ChipDefaultsM3(this.context, this.isEnabled) diff --git a/packages/flutter/lib/src/material/choice_chip.dart b/packages/flutter/lib/src/material/choice_chip.dart index 4514d7f6ed391..bd6f5ca456e1d 100644 --- a/packages/flutter/lib/src/material/choice_chip.dart +++ b/packages/flutter/lib/src/material/choice_chip.dart @@ -191,7 +191,7 @@ class ChoiceChip extends StatelessWidget // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _ChoiceChipDefaultsM3 extends ChipThemeData { const _ChoiceChipDefaultsM3(this.context, this.isEnabled, this.isSelected) @@ -212,7 +212,7 @@ class _ChoiceChipDefaultsM3 extends ChipThemeData { Color? get backgroundColor => null; @override - Color? get shadowColor => Theme.of(context).colorScheme.shadow; + Color? get shadowColor => Colors.transparent; @override Color? get surfaceTintColor => Theme.of(context).colorScheme.surfaceTint; diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index 95ab97582352e..e213304121242 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart @@ -1431,7 +1431,7 @@ class _DialogDefaultsM2 extends DialogTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _DialogDefaultsM3 extends DialogTheme { _DialogDefaultsM3(this.context) @@ -1476,7 +1476,7 @@ class _DialogDefaultsM3 extends DialogTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _DialogFullscreenDefaultsM3 extends DialogTheme { const _DialogFullscreenDefaultsM3(this.context); diff --git a/packages/flutter/lib/src/material/divider.dart b/packages/flutter/lib/src/material/divider.dart index 10e25866216bb..0894fc8ba4154 100644 --- a/packages/flutter/lib/src/material/divider.dart +++ b/packages/flutter/lib/src/material/divider.dart @@ -328,7 +328,7 @@ class _DividerDefaultsM2 extends DividerThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _DividerDefaultsM3 extends DividerThemeData { const _DividerDefaultsM3(this.context) : super( diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index c1d3ea3b5d5e4..622d4893d1958 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -827,7 +827,7 @@ class _DrawerDefaultsM2 extends DrawerThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _DrawerDefaultsM3 extends DrawerThemeData { const _DrawerDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/elevated_button.dart b/packages/flutter/lib/src/material/elevated_button.dart index cb98168e25215..94b6dfc51cc76 100644 --- a/packages/flutter/lib/src/material/elevated_button.dart +++ b/packages/flutter/lib/src/material/elevated_button.dart @@ -532,7 +532,7 @@ class _ElevatedButtonWithIconChild extends StatelessWidget { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _ElevatedButtonDefaultsM3 extends ButtonStyle { _ElevatedButtonDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/elevation_overlay.dart b/packages/flutter/lib/src/material/elevation_overlay.dart index 4f0dd43f85d26..cd47c6fe6c167 100644 --- a/packages/flutter/lib/src/material/elevation_overlay.dart +++ b/packages/flutter/lib/src/material/elevation_overlay.dart @@ -160,7 +160,7 @@ class _ElevationOpacity { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 // Surface tint opacities based on elevations according to the // Material Design 3 specification: diff --git a/packages/flutter/lib/src/material/filled_button.dart b/packages/flutter/lib/src/material/filled_button.dart index fa9793a6de29f..69efdde9d5825 100644 --- a/packages/flutter/lib/src/material/filled_button.dart +++ b/packages/flutter/lib/src/material/filled_button.dart @@ -505,7 +505,7 @@ class _FilledButtonWithIconChild extends StatelessWidget { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _FilledButtonDefaultsM3 extends ButtonStyle { _FilledButtonDefaultsM3(this.context) @@ -629,7 +629,7 @@ class _FilledButtonDefaultsM3 extends ButtonStyle { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _FilledTonalButtonDefaultsM3 extends ButtonStyle { _FilledTonalButtonDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/filter_chip.dart b/packages/flutter/lib/src/material/filter_chip.dart index 43440efdcc3ea..25b55d82c29ab 100644 --- a/packages/flutter/lib/src/material/filter_chip.dart +++ b/packages/flutter/lib/src/material/filter_chip.dart @@ -199,7 +199,7 @@ class FilterChip extends StatelessWidget // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _FilterChipDefaultsM3 extends ChipThemeData { const _FilterChipDefaultsM3(this.context, this.isEnabled, this.isSelected) @@ -220,7 +220,7 @@ class _FilterChipDefaultsM3 extends ChipThemeData { Color? get backgroundColor => null; @override - Color? get shadowColor => Theme.of(context).colorScheme.shadow; + Color? get shadowColor => Colors.transparent; @override Color? get surfaceTintColor => Theme.of(context).colorScheme.surfaceTint; diff --git a/packages/flutter/lib/src/material/floating_action_button.dart b/packages/flutter/lib/src/material/floating_action_button.dart index 49272c7408000..9e70d9185276e 100644 --- a/packages/flutter/lib/src/material/floating_action_button.dart +++ b/packages/flutter/lib/src/material/floating_action_button.dart @@ -804,7 +804,7 @@ class _FABDefaultsM2 extends FloatingActionButtonThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _FABDefaultsM3 extends FloatingActionButtonThemeData { _FABDefaultsM3(this.context, this.type, this.hasChild) diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart index c0817e3647e93..f19ea701bff5a 100644 --- a/packages/flutter/lib/src/material/icon_button.dart +++ b/packages/flutter/lib/src/material/icon_button.dart @@ -962,7 +962,7 @@ class _IconButtonDefaultMouseCursor extends MaterialStateProperty w // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _IconButtonDefaultsM3 extends ButtonStyle { _IconButtonDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/input_chip.dart b/packages/flutter/lib/src/material/input_chip.dart index fd3b6e0d146b6..fab4a2bc10351 100644 --- a/packages/flutter/lib/src/material/input_chip.dart +++ b/packages/flutter/lib/src/material/input_chip.dart @@ -249,7 +249,7 @@ class InputChip extends StatelessWidget // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _InputChipDefaultsM3 extends ChipThemeData { const _InputChipDefaultsM3(this.context, this.isEnabled) diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index f3fdfd0626d78..325170cf6d8ae 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -4555,7 +4555,7 @@ class _InputDecoratorDefaultsM2 extends InputDecorationTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _InputDecoratorDefaultsM3 extends InputDecorationTheme { _InputDecoratorDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/menu_anchor.dart b/packages/flutter/lib/src/material/menu_anchor.dart index 63035a99af6fb..06388ad0e3dab 100644 --- a/packages/flutter/lib/src/material/menu_anchor.dart +++ b/packages/flutter/lib/src/material/menu_anchor.dart @@ -3586,7 +3586,7 @@ bool _platformSupportsAccelerators() { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _MenuBarDefaultsM3 extends MenuStyle { _MenuBarDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/navigation_bar.dart b/packages/flutter/lib/src/material/navigation_bar.dart index 103f2ff996dfc..cf0ff225371db 100644 --- a/packages/flutter/lib/src/material/navigation_bar.dart +++ b/packages/flutter/lib/src/material/navigation_bar.dart @@ -1354,7 +1354,7 @@ class _NavigationBarDefaultsM2 extends NavigationBarThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _NavigationBarDefaultsM3 extends NavigationBarThemeData { _NavigationBarDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/navigation_drawer.dart b/packages/flutter/lib/src/material/navigation_drawer.dart index c160f6aea456f..a81ca069d04f9 100644 --- a/packages/flutter/lib/src/material/navigation_drawer.dart +++ b/packages/flutter/lib/src/material/navigation_drawer.dart @@ -658,7 +658,7 @@ bool _isForwardOrCompleted(Animation animation) { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _NavigationDrawerDefaultsM3 extends NavigationDrawerThemeData { const _NavigationDrawerDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/navigation_rail.dart b/packages/flutter/lib/src/material/navigation_rail.dart index a7e14275644c2..7454d24ccfd30 100644 --- a/packages/flutter/lib/src/material/navigation_rail.dart +++ b/packages/flutter/lib/src/material/navigation_rail.dart @@ -1047,7 +1047,7 @@ class _NavigationRailDefaultsM2 extends NavigationRailThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _NavigationRailDefaultsM3 extends NavigationRailThemeData { _NavigationRailDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/outlined_button.dart b/packages/flutter/lib/src/material/outlined_button.dart index d60caaa75ab68..b692cec239724 100644 --- a/packages/flutter/lib/src/material/outlined_button.dart +++ b/packages/flutter/lib/src/material/outlined_button.dart @@ -453,7 +453,7 @@ class _OutlinedButtonWithIconChild extends StatelessWidget { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _OutlinedButtonDefaultsM3 extends ButtonStyle { _OutlinedButtonDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index f61d8b065ddd5..26c3a5efe712a 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -1375,7 +1375,7 @@ class _PopupMenuDefaultsM2 extends PopupMenuThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _PopupMenuDefaultsM3 extends PopupMenuThemeData { _PopupMenuDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/progress_indicator.dart b/packages/flutter/lib/src/material/progress_indicator.dart index 75eba41534840..20c6f5e27745e 100644 --- a/packages/flutter/lib/src/material/progress_indicator.dart +++ b/packages/flutter/lib/src/material/progress_indicator.dart @@ -925,7 +925,7 @@ class _LinearProgressIndicatorDefaultsM2 extends ProgressIndicatorThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _CircularProgressIndicatorDefaultsM3 extends ProgressIndicatorThemeData { _CircularProgressIndicatorDefaultsM3(this.context); diff --git a/packages/flutter/lib/src/material/radio.dart b/packages/flutter/lib/src/material/radio.dart index 7be9e9cf9d618..ed88d2a2e02eb 100644 --- a/packages/flutter/lib/src/material/radio.dart +++ b/packages/flutter/lib/src/material/radio.dart @@ -537,7 +537,7 @@ class _RadioDefaultsM2 extends RadioThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _RadioDefaultsM3 extends RadioThemeData { _RadioDefaultsM3(this.context); diff --git a/packages/flutter/lib/src/material/segmented_button.dart b/packages/flutter/lib/src/material/segmented_button.dart index 8c2e3fbc61af9..050c15f85edfd 100644 --- a/packages/flutter/lib/src/material/segmented_button.dart +++ b/packages/flutter/lib/src/material/segmented_button.dart @@ -720,7 +720,7 @@ class _RenderSegmentedButton extends RenderBox with // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _SegmentedButtonDefaultsM3 extends SegmentedButtonThemeData { _SegmentedButtonDefaultsM3(this.context); diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index 5cae288f7d687..f95acd994e7d6 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -1888,7 +1888,7 @@ class _SliderDefaultsM2 extends SliderThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _SliderDefaultsM3 extends SliderThemeData { _SliderDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/snack_bar.dart b/packages/flutter/lib/src/material/snack_bar.dart index 79b52bfa68167..f4152f7804a8e 100644 --- a/packages/flutter/lib/src/material/snack_bar.dart +++ b/packages/flutter/lib/src/material/snack_bar.dart @@ -828,7 +828,7 @@ class _SnackbarDefaultsM2 extends SnackBarThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _SnackbarDefaultsM3 extends SnackBarThemeData { _SnackbarDefaultsM3(this.context); diff --git a/packages/flutter/lib/src/material/switch.dart b/packages/flutter/lib/src/material/switch.dart index a2047a86cd861..dd5e2561fdfac 100644 --- a/packages/flutter/lib/src/material/switch.dart +++ b/packages/flutter/lib/src/material/switch.dart @@ -1641,7 +1641,7 @@ class _SwitchDefaultsM2 extends SwitchThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _SwitchDefaultsM3 extends SwitchThemeData { _SwitchDefaultsM3(BuildContext context) diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 21f6f9234e056..08b38723a20f5 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -1912,7 +1912,7 @@ class _TabsDefaultsM2 extends TabBarTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _TabsDefaultsM3 extends TabBarTheme { _TabsDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/text_button.dart b/packages/flutter/lib/src/material/text_button.dart index 03e07176eb0a2..a337ac6d47a19 100644 --- a/packages/flutter/lib/src/material/text_button.dart +++ b/packages/flutter/lib/src/material/text_button.dart @@ -530,7 +530,7 @@ class _TextButtonWithIconChild extends StatelessWidget { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _TextButtonDefaultsM3 extends ButtonStyle { _TextButtonDefaultsM3(this.context) diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index c2627bd6a8a09..d4f0eaf1ca0a0 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -1455,7 +1455,7 @@ TextStyle _m2CounterErrorStyle(BuildContext context) => // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 TextStyle _m3InputStyle(BuildContext context) => Theme.of(context).textTheme.bodyLarge!; diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index 08dd5a237d416..fca0a4fffb18f 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -3043,7 +3043,7 @@ class VisualDensity with Diagnosticable { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 const ColorScheme _colorSchemeLightM3 = ColorScheme( brightness: Brightness.light, diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index a041beaab6ce0..61cb7f4e17bbe 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -3392,9 +3392,9 @@ class _TimePickerDefaultsM2 extends _TimePickerDefaults { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 -// Generated version v0_143 +// Generated version v0_150 class _TimePickerDefaultsM3 extends _TimePickerDefaults { _TimePickerDefaultsM3(this.context); @@ -3509,7 +3509,7 @@ class _TimePickerDefaultsM3 extends _TimePickerDefaults { @override Color get dialBackgroundColor { - return _colors.onSurfaceVariant.withOpacity(_colors.brightness == Brightness.dark ? 0.12 : 0.08); + return _colors.surfaceVariant.withOpacity(_colors.brightness == Brightness.dark ? 0.12 : 0.08); } @override diff --git a/packages/flutter/lib/src/material/typography.dart b/packages/flutter/lib/src/material/typography.dart index 3d573451e6e9a..a03c31f3b2de2 100644 --- a/packages/flutter/lib/src/material/typography.dart +++ b/packages/flutter/lib/src/material/typography.dart @@ -759,7 +759,7 @@ class Typography with Diagnosticable { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_143 +// Token database version: v0_150 class _M3Typography { _M3Typography._(); From b9ead37244b1bfc4d35f36440088f35949738e41 Mon Sep 17 00:00:00 2001 From: Ahmed Ashour Date: Tue, 3 Jan 2023 21:51:48 +0100 Subject: [PATCH 05/18] Simplify null check. (#117026) * Simplify null check. * Simplify null check. * Simplify null check. * Fix. --- packages/flutter_tools/lib/src/device.dart | 54 +++++++++---------- .../application_package_test.dart | 6 +-- .../test/general.shard/dap/mocks.dart | 6 +-- .../integration.shard/hot_reload_test.dart | 4 +- .../template_manifest_test.dart | 2 +- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 80161152bb608..67e6cab36cb97 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -1004,45 +1004,45 @@ class DebuggingOptions { static DebuggingOptions fromJson(Map json, BuildInfo buildInfo) => DebuggingOptions._( buildInfo: buildInfo, - debuggingEnabled: (json['debuggingEnabled'] as bool?)!, - startPaused: (json['startPaused'] as bool?)!, - dartFlags: (json['dartFlags'] as String?)!, - dartEntrypointArgs: ((json['dartEntrypointArgs'] as List?)?.cast())!, - disableServiceAuthCodes: (json['disableServiceAuthCodes'] as bool?)!, - enableDds: (json['enableDds'] as bool?)!, - cacheStartupProfile: (json['cacheStartupProfile'] as bool?)!, - enableSoftwareRendering: (json['enableSoftwareRendering'] as bool?)!, - skiaDeterministicRendering: (json['skiaDeterministicRendering'] as bool?)!, - traceSkia: (json['traceSkia'] as bool?)!, + debuggingEnabled: json['debuggingEnabled']! as bool, + startPaused: json['startPaused']! as bool, + dartFlags: json['dartFlags']! as String, + dartEntrypointArgs: (json['dartEntrypointArgs']! as List).cast(), + disableServiceAuthCodes: json['disableServiceAuthCodes']! as bool, + enableDds: json['enableDds']! as bool, + cacheStartupProfile: json['cacheStartupProfile']! as bool, + enableSoftwareRendering: json['enableSoftwareRendering']! as bool, + skiaDeterministicRendering: json['skiaDeterministicRendering']! as bool, + traceSkia: json['traceSkia']! as bool, traceAllowlist: json['traceAllowlist'] as String?, traceSkiaAllowlist: json['traceSkiaAllowlist'] as String?, - traceSystrace: (json['traceSystrace'] as bool?)!, - endlessTraceBuffer: (json['endlessTraceBuffer'] as bool?)!, - dumpSkpOnShaderCompilation: (json['dumpSkpOnShaderCompilation'] as bool?)!, - cacheSkSL: (json['cacheSkSL'] as bool?)!, - purgePersistentCache: (json['purgePersistentCache'] as bool?)!, - useTestFonts: (json['useTestFonts'] as bool?)!, - verboseSystemLogs: (json['verboseSystemLogs'] as bool?)!, + traceSystrace: json['traceSystrace']! as bool, + endlessTraceBuffer: json['endlessTraceBuffer']! as bool, + dumpSkpOnShaderCompilation: json['dumpSkpOnShaderCompilation']! as bool, + cacheSkSL: json['cacheSkSL']! as bool, + purgePersistentCache: json['purgePersistentCache']! as bool, + useTestFonts: json['useTestFonts']! as bool, + verboseSystemLogs: json['verboseSystemLogs']! as bool, hostVmServicePort: json['hostVmServicePort'] as int? , deviceVmServicePort: json['deviceVmServicePort'] as int?, - disablePortPublication: (json['disablePortPublication'] as bool?)!, + disablePortPublication: json['disablePortPublication']! as bool, ddsPort: json['ddsPort'] as int?, devToolsServerAddress: json['devToolsServerAddress'] != null ? Uri.parse(json['devToolsServerAddress']! as String) : null, port: json['port'] as String?, hostname: json['hostname'] as String?, webEnableExposeUrl: json['webEnableExposeUrl'] as bool?, - webUseSseForDebugProxy: (json['webUseSseForDebugProxy'] as bool?)!, - webUseSseForDebugBackend: (json['webUseSseForDebugBackend'] as bool?)!, - webUseSseForInjectedClient: (json['webUseSseForInjectedClient'] as bool?)!, - webRunHeadless: (json['webRunHeadless'] as bool?)!, + webUseSseForDebugProxy: json['webUseSseForDebugProxy']! as bool, + webUseSseForDebugBackend: json['webUseSseForDebugBackend']! as bool, + webUseSseForInjectedClient: json['webUseSseForInjectedClient']! as bool, + webRunHeadless: json['webRunHeadless']! as bool, webBrowserDebugPort: json['webBrowserDebugPort'] as int?, - webBrowserFlags: ((json['webBrowserFlags'] as List?)?.cast())!, - webEnableExpressionEvaluation: (json['webEnableExpressionEvaluation'] as bool?)!, + webBrowserFlags: (json['webBrowserFlags']! as List).cast(), + webEnableExpressionEvaluation: json['webEnableExpressionEvaluation']! as bool, webLaunchUrl: json['webLaunchUrl'] as String?, vmserviceOutFile: json['vmserviceOutFile'] as String?, - fastStart: (json['fastStart'] as bool?)!, - nullAssertions: (json['nullAssertions'] as bool?)!, - nativeNullAssertions: (json['nativeNullAssertions'] as bool?)!, + fastStart: json['fastStart']! as bool, + nullAssertions: json['nullAssertions']! as bool, + nativeNullAssertions: json['nativeNullAssertions']! as bool, enableImpeller: (json['enableImpeller'] as bool?) ?? false, uninstallFirst: (json['uninstallFirst'] as bool?) ?? false, enableDartProfiling: (json['enableDartProfiling'] as bool?) ?? true, diff --git a/packages/flutter_tools/test/general.shard/application_package_test.dart b/packages/flutter_tools/test/general.shard/application_package_test.dart index 1fa60d37c6873..ce2d400b7faf4 100644 --- a/packages/flutter_tools/test/general.shard/application_package_test.dart +++ b/packages/flutter_tools/test/general.shard/application_package_test.dart @@ -292,7 +292,7 @@ void main() { globals.fs.directory('bundle.app').createSync(); globals.fs.file('bundle.app/Info.plist').createSync(); testPlistParser.setProperty('CFBundleIdentifier', 'fooBundleId'); - final PrebuiltIOSApp iosApp = (IOSApp.fromPrebuiltApp(globals.fs.file('bundle.app')) as PrebuiltIOSApp?)!; + final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(globals.fs.file('bundle.app'))! as PrebuiltIOSApp; expect(testLogger.errorText, isEmpty); expect(iosApp.uncompressedBundle.path, 'bundle.app'); expect(iosApp.id, 'fooBundleId'); @@ -343,7 +343,7 @@ void main() { .file(globals.fs.path.join(bundleAppDir.path, 'Info.plist')) .createSync(); }; - final PrebuiltIOSApp iosApp = (IOSApp.fromPrebuiltApp(globals.fs.file('app.ipa')) as PrebuiltIOSApp?)!; + final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(globals.fs.file('app.ipa'))! as PrebuiltIOSApp; expect(testLogger.errorText, isEmpty); expect(iosApp.uncompressedBundle.path, endsWith('bundle.app')); expect(iosApp.id, 'fooBundleId'); @@ -594,7 +594,7 @@ void main() { testUsingContext('Success with far file', () { globals.fs.file('bundle.far').createSync(); - final PrebuiltFuchsiaApp fuchsiaApp = (FuchsiaApp.fromPrebuiltApp(globals.fs.file('bundle.far')) as PrebuiltFuchsiaApp?)!; + final PrebuiltFuchsiaApp fuchsiaApp = FuchsiaApp.fromPrebuiltApp(globals.fs.file('bundle.far'))! as PrebuiltFuchsiaApp; expect(testLogger.errorText, isEmpty); expect(fuchsiaApp.id, 'bundle.far'); expect(fuchsiaApp.applicationPackage.path, globals.fs.file('bundle.far').path); diff --git a/packages/flutter_tools/test/general.shard/dap/mocks.dart b/packages/flutter_tools/test/general.shard/dap/mocks.dart index 07618e93039be..fba7f22cf6e34 100644 --- a/packages/flutter_tools/test/general.shard/dap/mocks.dart +++ b/packages/flutter_tools/test/general.shard/dap/mocks.dart @@ -117,8 +117,8 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { // Pretend to be the client, delegating any reverse-requests to the relevant // handler that is provided by the test. if (message is Event && message.event == 'flutter.forwardedRequest') { - final Map body = (message.body as Map?)!; - final String method = (body['method'] as String?)!; + final Map body = message.body! as Map; + final String method = body['method']! as String; final Map? params = body['params'] as Map?; final Object? result = _handleReverseRequest(method, params); @@ -138,7 +138,7 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { Object? _handleReverseRequest(String method, Map? params) { switch (method) { case 'app.exposeUrl': - final String url = (params!['url'] as String?)!; + final String url = params!['url']! as String; return exposeUrlHandler!(url); default: throw ArgumentError('Reverse-request $method is unknown'); diff --git a/packages/flutter_tools/test/integration.shard/hot_reload_test.dart b/packages/flutter_tools/test/integration.shard/hot_reload_test.dart index 71e91354cb247..0d5f132f486cb 100644 --- a/packages/flutter_tools/test/integration.shard/hot_reload_test.dart +++ b/packages/flutter_tools/test/integration.shard/hot_reload_test.dart @@ -193,6 +193,6 @@ bool _isHotReloadCompletionEvent(Map? event) { return event != null && event['event'] == 'app.progress' && event['params'] != null && - (event['params'] as Map?)!['progressId'] == 'hot.reload' && - (event['params'] as Map?)!['finished'] == true; + (event['params']! as Map)['progressId'] == 'hot.reload' && + (event['params']! as Map)['finished'] == true; } diff --git a/packages/flutter_tools/test/integration.shard/template_manifest_test.dart b/packages/flutter_tools/test/integration.shard/template_manifest_test.dart index 6709b13742ae7..80741f70c32cf 100644 --- a/packages/flutter_tools/test/integration.shard/template_manifest_test.dart +++ b/packages/flutter_tools/test/integration.shard/template_manifest_test.dart @@ -14,7 +14,7 @@ void main() { fileSystem.file('templates/template_manifest.json').readAsStringSync(), ) as Map; final Set declaredFileList = Set.from( - (manifest['files'] as List?)!.cast().map(fileSystem.path.toUri)); + (manifest['files']! as List).cast().map(fileSystem.path.toUri)); final Set activeTemplateList = fileSystem.directory('templates') .listSync(recursive: true) From 084be5e6db6f22d1d242e8e35878fa499f34f89e Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 3 Jan 2023 16:30:36 -0500 Subject: [PATCH 06/18] Roll Flutter Engine from 7e51aef0a1be to 1d2ba73d1059 (9 revisions) (#117923) * 3e1b0dcb2 Roll Dart SDK from 881c0b56a1f7 to 617e70c95f5b (1 revision) (flutter/engine#38597) * 8b17efed8 Roll Fuchsia Linux SDK from UAq0LO56_kbgA_BUQ... to LA5kW39Gec7KvvM7x... (flutter/engine#38598) * 27960a700 [Impeller Scene] Import animation data (flutter/engine#38583) * b5acb2099 Roll Skia from 809e328ed55c to 697f9b541a0e (1 revision) (flutter/engine#38599) * dd0335b34 Roll Skia from 697f9b541a0e to 15d36b15bca1 (1 revision) (flutter/engine#38601) * adda2e80c [Impeller Scene] Animation binding and playback (flutter/engine#38595) * 71a296d53 Roll Fuchsia Linux SDK from LA5kW39Gec7KvvM7x... to rPo4_TYHCtkoOfRup... (flutter/engine#38607) * bde8d4524 Implement ITextProvider and ITextRangeProvider for UIA (flutter/engine#38538) * 1d2ba73d1 [Windows] Make the engine own the cursor plugin (flutter/engine#38570) --- bin/internal/engine.version | 2 +- bin/internal/fuchsia-linux.version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index a3825e26ecad6..752c8e6ca7ec7 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -7e51aef0a1be6d97d476ec196b03cc6224527780 +1d2ba73d1059544564e644055d4366956e581e87 diff --git a/bin/internal/fuchsia-linux.version b/bin/internal/fuchsia-linux.version index ce76237833135..a26b781669eea 100644 --- a/bin/internal/fuchsia-linux.version +++ b/bin/internal/fuchsia-linux.version @@ -1 +1 @@ -UAq0LO56_kbgA_BUQbFrAn6tjxoAjmOxZpzkVFbsn0oC +rPo4_TYHCtkoOfRups4wfQHs9aQKCjTqh7_VasyQt_IC From fdc25a170087ac2308fb5486e9cf76ef62f57b5a Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Tue, 3 Jan 2023 13:34:08 -0800 Subject: [PATCH 07/18] Reland "Remove single-view assumption from ScrollPhysics (#117503)" (#117916) This reverts commit c956121ac098b91061bfc0a93c930121ff3279ba. --- .../lib/demo/animation/home.dart | 16 ++++----- .../widgets/draggable_scrollable_sheet.dart | 8 ++--- .../src/widgets/list_wheel_scroll_view.dart | 13 ++++--- .../lib/src/widgets/nested_scroll_view.dart | 4 +++ .../flutter/lib/src/widgets/page_view.dart | 7 +++- .../lib/src/widgets/scroll_metrics.dart | 10 ++++++ .../lib/src/widgets/scroll_physics.dart | 34 +++++++++++++------ .../lib/src/widgets/scroll_position.dart | 5 +++ .../scroll_position_with_single_context.dart | 2 +- .../flutter/lib/src/widgets/scrollable.dart | 6 ++++ .../flutter/test/material/scrollbar_test.dart | 1 + .../flutter/test/widgets/page_view_test.dart | 1 + .../test/widgets/scroll_physics_test.dart | 29 ++++++++++------ .../widgets/scrollable_animations_test.dart | 4 +-- .../flutter/test/widgets/scrollbar_test.dart | 1 + .../test/widgets/slivers_evil_test.dart | 2 +- 16 files changed, 102 insertions(+), 41 deletions(-) diff --git a/dev/integration_tests/flutter_gallery/lib/demo/animation/home.dart b/dev/integration_tests/flutter_gallery/lib/demo/animation/home.dart index ce08027c61413..593e55db54301 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/animation/home.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/animation/home.dart @@ -371,14 +371,14 @@ class _SnappingScrollPhysics extends ClampingScrollPhysics { return _SnappingScrollPhysics(parent: buildParent(ancestor), midScrollOffset: midScrollOffset); } - Simulation _toMidScrollOffsetSimulation(double offset, double dragVelocity) { + Simulation _toMidScrollOffsetSimulation(double offset, double dragVelocity, ScrollMetrics metrics) { final double velocity = math.max(dragVelocity, minFlingVelocity); - return ScrollSpringSimulation(spring, offset, midScrollOffset, velocity, tolerance: tolerance); + return ScrollSpringSimulation(spring, offset, midScrollOffset, velocity, tolerance: toleranceFor(metrics)); } - Simulation _toZeroScrollOffsetSimulation(double offset, double dragVelocity) { + Simulation _toZeroScrollOffsetSimulation(double offset, double dragVelocity, ScrollMetrics metrics) { final double velocity = math.max(dragVelocity, minFlingVelocity); - return ScrollSpringSimulation(spring, offset, 0.0, velocity, tolerance: tolerance); + return ScrollSpringSimulation(spring, offset, 0.0, velocity, tolerance: toleranceFor(metrics)); } @override @@ -396,10 +396,10 @@ class _SnappingScrollPhysics extends ClampingScrollPhysics { return simulation; } if (dragVelocity > 0.0) { - return _toMidScrollOffsetSimulation(offset, dragVelocity); + return _toMidScrollOffsetSimulation(offset, dragVelocity, position); } if (dragVelocity < 0.0) { - return _toZeroScrollOffsetSimulation(offset, dragVelocity); + return _toZeroScrollOffsetSimulation(offset, dragVelocity, position); } } else { // The user ended the drag with little or no velocity. If they @@ -408,10 +408,10 @@ class _SnappingScrollPhysics extends ClampingScrollPhysics { // otherwise snap to zero. final double snapThreshold = midScrollOffset / 2.0; if (offset >= snapThreshold && offset < midScrollOffset) { - return _toMidScrollOffsetSimulation(offset, dragVelocity); + return _toMidScrollOffsetSimulation(offset, dragVelocity, position); } if (offset > 0.0 && offset < snapThreshold) { - return _toZeroScrollOffsetSimulation(offset, dragVelocity); + return _toZeroScrollOffsetSimulation(offset, dragVelocity, position); } } return simulation; diff --git a/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart b/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart index 7df9c4934dbfe..c2d55783c71f4 100644 --- a/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart +++ b/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart @@ -902,7 +902,7 @@ class _DraggableScrollableSheetScrollPosition extends ScrollPositionWithSingleCo bool get _isAtSnapSize { return extent.snapSizes.any( (double snapSize) { - return (extent.currentSize - snapSize).abs() <= extent.pixelsToSize(physics.tolerance.distance); + return (extent.currentSize - snapSize).abs() <= extent.pixelsToSize(physics.toleranceFor(this).distance); }, ); } @@ -937,7 +937,7 @@ class _DraggableScrollableSheetScrollPosition extends ScrollPositionWithSingleCo initialVelocity: velocity, pixelSnapSize: extent.pixelSnapSizes, snapAnimationDuration: extent.snapAnimationDuration, - tolerance: physics.tolerance, + tolerance: physics.toleranceFor(this), ); } else { // The iOS bouncing simulation just isn't right here - once we delegate @@ -946,7 +946,7 @@ class _DraggableScrollableSheetScrollPosition extends ScrollPositionWithSingleCo // Run the simulation in terms of pixels, not extent. position: extent.currentPixels, velocity: velocity, - tolerance: physics.tolerance, + tolerance: physics.toleranceFor(this), ); } @@ -965,7 +965,7 @@ class _DraggableScrollableSheetScrollPosition extends ScrollPositionWithSingleCo // Make sure we pass along enough velocity to keep scrolling - otherwise // we just "bounce" off the top making it look like the list doesn't // have more to scroll. - velocity = ballisticController.velocity + (physics.tolerance.velocity * ballisticController.velocity.sign); + velocity = ballisticController.velocity + (physics.toleranceFor(this).velocity * ballisticController.velocity.sign); super.goBallistic(velocity); ballisticController.stop(); } else if (ballisticController.isCompleted) { diff --git a/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart b/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart index caff1e9dab87d..8481bf471774d 100644 --- a/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart @@ -316,6 +316,7 @@ class FixedExtentMetrics extends FixedScrollMetrics { required super.viewportDimension, required super.axisDirection, required this.itemIndex, + required super.devicePixelRatio, }); @override @@ -326,6 +327,7 @@ class FixedExtentMetrics extends FixedScrollMetrics { double? viewportDimension, AxisDirection? axisDirection, int? itemIndex, + double? devicePixelRatio, }) { return FixedExtentMetrics( minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null), @@ -334,6 +336,7 @@ class FixedExtentMetrics extends FixedScrollMetrics { viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null), axisDirection: axisDirection ?? this.axisDirection, itemIndex: itemIndex ?? this.itemIndex, + devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, ); } @@ -399,6 +402,7 @@ class _FixedExtentScrollPosition extends ScrollPositionWithSingleContext impleme double? viewportDimension, AxisDirection? axisDirection, int? itemIndex, + double? devicePixelRatio, }) { return FixedExtentMetrics( minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null), @@ -407,6 +411,7 @@ class _FixedExtentScrollPosition extends ScrollPositionWithSingleContext impleme viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null), axisDirection: axisDirection ?? this.axisDirection, itemIndex: itemIndex ?? this.itemIndex, + devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, ); } } @@ -505,8 +510,8 @@ class FixedExtentScrollPhysics extends ScrollPhysics { // Scenario 3: // If there's no velocity and we're already at where we intend to land, // do nothing. - if (velocity.abs() < tolerance.velocity - && (settlingPixels - metrics.pixels).abs() < tolerance.distance) { + if (velocity.abs() < toleranceFor(position).velocity + && (settlingPixels - metrics.pixels).abs() < toleranceFor(position).distance) { return null; } @@ -519,7 +524,7 @@ class FixedExtentScrollPhysics extends ScrollPhysics { metrics.pixels, settlingPixels, velocity, - tolerance: tolerance, + tolerance: toleranceFor(position), ); } @@ -530,7 +535,7 @@ class FixedExtentScrollPhysics extends ScrollPhysics { metrics.pixels, settlingPixels, velocity, - tolerance.velocity * velocity.sign, + toleranceFor(position).velocity * velocity.sign, ); } } diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index 2987b2b04c423..822f59888d6d1 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -526,6 +526,7 @@ class _NestedScrollMetrics extends FixedScrollMetrics { required super.pixels, required super.viewportDimension, required super.axisDirection, + required super.devicePixelRatio, required this.minRange, required this.maxRange, required this.correctionOffset, @@ -538,6 +539,7 @@ class _NestedScrollMetrics extends FixedScrollMetrics { double? pixels, double? viewportDimension, AxisDirection? axisDirection, + double? devicePixelRatio, double? minRange, double? maxRange, double? correctionOffset, @@ -548,6 +550,7 @@ class _NestedScrollMetrics extends FixedScrollMetrics { pixels: pixels ?? (hasPixels ? this.pixels : null), viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null), axisDirection: axisDirection ?? this.axisDirection, + devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, minRange: minRange ?? this.minRange, maxRange: maxRange ?? this.maxRange, correctionOffset: correctionOffset ?? this.correctionOffset, @@ -815,6 +818,7 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont minRange: minRange, maxRange: maxRange, correctionOffset: correctionOffset, + devicePixelRatio: _outerPosition!.devicePixelRatio, ); } diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart index f8ef1c1cfa649..4ef93eed89090 100644 --- a/packages/flutter/lib/src/widgets/page_view.dart +++ b/packages/flutter/lib/src/widgets/page_view.dart @@ -273,6 +273,7 @@ class PageMetrics extends FixedScrollMetrics { required super.viewportDimension, required super.axisDirection, required this.viewportFraction, + required super.devicePixelRatio, }); @override @@ -283,6 +284,7 @@ class PageMetrics extends FixedScrollMetrics { double? viewportDimension, AxisDirection? axisDirection, double? viewportFraction, + double? devicePixelRatio, }) { return PageMetrics( minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null), @@ -291,6 +293,7 @@ class PageMetrics extends FixedScrollMetrics { viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null), axisDirection: axisDirection ?? this.axisDirection, viewportFraction: viewportFraction ?? this.viewportFraction, + devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, ); } @@ -493,6 +496,7 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri double? viewportDimension, AxisDirection? axisDirection, double? viewportFraction, + double? devicePixelRatio, }) { return PageMetrics( minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null), @@ -501,6 +505,7 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null), axisDirection: axisDirection ?? this.axisDirection, viewportFraction: viewportFraction ?? this.viewportFraction, + devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, ); } } @@ -573,7 +578,7 @@ class PageScrollPhysics extends ScrollPhysics { (velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) { return super.createBallisticSimulation(position, velocity); } - final Tolerance tolerance = this.tolerance; + final Tolerance tolerance = toleranceFor(position); final double target = _getTargetPixels(position, tolerance, velocity); if (target != position.pixels) { return ScrollSpringSimulation(spring, position.pixels, target, velocity, tolerance: tolerance); diff --git a/packages/flutter/lib/src/widgets/scroll_metrics.dart b/packages/flutter/lib/src/widgets/scroll_metrics.dart index 10c20da60c46b..ca7cf48ce3cf0 100644 --- a/packages/flutter/lib/src/widgets/scroll_metrics.dart +++ b/packages/flutter/lib/src/widgets/scroll_metrics.dart @@ -46,6 +46,7 @@ mixin ScrollMetrics { double? pixels, double? viewportDimension, AxisDirection? axisDirection, + double? devicePixelRatio, }) { return FixedScrollMetrics( minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null), @@ -53,6 +54,7 @@ mixin ScrollMetrics { pixels: pixels ?? (hasPixels ? this.pixels : null), viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null), axisDirection: axisDirection ?? this.axisDirection, + devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, ); } @@ -124,6 +126,10 @@ mixin ScrollMetrics { /// The quantity of content conceptually "below" the viewport in the scrollable. /// This is the content below the content described by [extentInside]. double get extentAfter => math.max(maxScrollExtent - pixels, 0.0); + + /// The [FlutterView.devicePixelRatio] of the view that the [Scrollable] + /// associated with this metrics object is drawn into. + double get devicePixelRatio; } /// An immutable snapshot of values associated with a [Scrollable] viewport. @@ -137,6 +143,7 @@ class FixedScrollMetrics with ScrollMetrics { required double? pixels, required double? viewportDimension, required this.axisDirection, + required this.devicePixelRatio, }) : _minScrollExtent = minScrollExtent, _maxScrollExtent = maxScrollExtent, _pixels = pixels, @@ -170,6 +177,9 @@ class FixedScrollMetrics with ScrollMetrics { @override final AxisDirection axisDirection; + @override + final double devicePixelRatio; + @override String toString() { return '${objectRuntimeType(this, 'FixedScrollMetrics')}(${extentBefore.toStringAsFixed(1)}..[${extentInside.toStringAsFixed(1)}]..${extentAfter.toStringAsFixed(1)})'; diff --git a/packages/flutter/lib/src/widgets/scroll_physics.dart b/packages/flutter/lib/src/widgets/scroll_physics.dart index e125e5d21729f..bca75a02283a4 100644 --- a/packages/flutter/lib/src/widgets/scroll_physics.dart +++ b/packages/flutter/lib/src/widgets/scroll_physics.dart @@ -6,6 +6,7 @@ import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; +import 'package:flutter/painting.dart' show AxisDirection; import 'package:flutter/physics.dart'; import 'binding.dart' show WidgetsBinding; @@ -383,16 +384,29 @@ class ScrollPhysics { /// The spring to use for ballistic simulations. SpringDescription get spring => parent?.spring ?? _kDefaultSpring; - /// The default accuracy to which scrolling is computed. - static final Tolerance _kDefaultTolerance = Tolerance( - // TODO(ianh): Handle the case of the device pixel ratio changing. - // TODO(ianh): Get this from the local MediaQuery not dart:ui's window object. - velocity: 1.0 / (0.050 * WidgetsBinding.instance.window.devicePixelRatio), // logical pixels per second - distance: 1.0 / WidgetsBinding.instance.window.devicePixelRatio, // logical pixels - ); + /// Deprecated. Call [toleranceFor] instead. + @Deprecated( + 'Call toleranceFor instead. ' + 'This feature was deprecated after v3.7.0-13.0.pre.', + ) + Tolerance get tolerance { + return toleranceFor(FixedScrollMetrics( + minScrollExtent: null, + maxScrollExtent: null, + pixels: null, + viewportDimension: null, + axisDirection: AxisDirection.down, + devicePixelRatio: WidgetsBinding.instance.window.devicePixelRatio, + )); + } /// The tolerance to use for ballistic simulations. - Tolerance get tolerance => parent?.tolerance ?? _kDefaultTolerance; + Tolerance toleranceFor(ScrollMetrics metrics) { + return parent?.toleranceFor(metrics) ?? Tolerance( + velocity: 1.0 / (0.050 * metrics.devicePixelRatio), // logical pixels per second + distance: 1.0 / metrics.devicePixelRatio, // logical pixels + ); + } /// The minimum distance an input pointer drag must have moved to /// to be considered a scroll fling gesture. @@ -696,7 +710,7 @@ class BouncingScrollPhysics extends ScrollPhysics { @override Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) { - final Tolerance tolerance = this.tolerance; + final Tolerance tolerance = toleranceFor(position); if (velocity.abs() >= tolerance.velocity || position.outOfRange) { double constantDeceleration; switch (decelerationRate) { @@ -840,7 +854,7 @@ class ClampingScrollPhysics extends ScrollPhysics { @override Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) { - final Tolerance tolerance = this.tolerance; + final Tolerance tolerance = toleranceFor(position); if (position.outOfRange) { double? end; if (position.pixels > position.maxScrollExtent) { diff --git a/packages/flutter/lib/src/widgets/scroll_position.dart b/packages/flutter/lib/src/widgets/scroll_position.dart index db363517c3725..9eb723d23b625 100644 --- a/packages/flutter/lib/src/widgets/scroll_position.dart +++ b/packages/flutter/lib/src/widgets/scroll_position.dart @@ -12,6 +12,7 @@ import 'package:flutter/scheduler.dart'; import 'basic.dart'; import 'framework.dart'; +import 'media_query.dart'; import 'notification_listener.dart'; import 'page_storage.dart'; import 'scroll_activity.dart'; @@ -19,6 +20,7 @@ import 'scroll_context.dart'; import 'scroll_metrics.dart'; import 'scroll_notification.dart'; import 'scroll_physics.dart'; +import 'view.dart'; export 'scroll_activity.dart' show ScrollHoldController; @@ -242,6 +244,9 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { isScrollingNotifier.value = activity!.isScrolling; } + @override + double get devicePixelRatio => MediaQuery.maybeDevicePixelRatioOf(context.storageContext) ?? View.of(context.storageContext).devicePixelRatio; + /// Update the scroll position ([pixels]) to a given pixel value. /// /// This should only be called by the current [ScrollActivity], either during diff --git a/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart b/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart index 86ef6966d8f3e..06e7325f5d21a 100644 --- a/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart +++ b/packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart @@ -176,7 +176,7 @@ class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollAc required Duration duration, required Curve curve, }) { - if (nearEqual(to, pixels, physics.tolerance.distance)) { + if (nearEqual(to, pixels, physics.toleranceFor(this).distance)) { // Skip the animation, go straight to the position as we are already close. jumpTo(to); return Future.value(); diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 5f32d8ffd3183..fca3892d321a7 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -727,6 +727,12 @@ class ScrollableState extends State with TickerProviderStateMixin, R } void _handleDragCancel() { + if (_gestureDetectorKey.currentContext == null) { + // The cancel was caused by the GestureDetector getting disposed, which + // means we will get disposed momentarily as well and shouldn't do + // any work. + return; + } // _hold might be null if the drag started. // _drag might be null if the drag activity ended and called _disposeDrag. assert(_hold == null || _drag == null); diff --git a/packages/flutter/test/material/scrollbar_test.dart b/packages/flutter/test/material/scrollbar_test.dart index eb0aa6811d896..bcf7f7de2b0be 100644 --- a/packages/flutter/test/material/scrollbar_test.dart +++ b/packages/flutter/test/material/scrollbar_test.dart @@ -148,6 +148,7 @@ void main() { pixels: 0.0, viewportDimension: 100.0, axisDirection: AxisDirection.down, + devicePixelRatio: tester.binding.window.devicePixelRatio, ); scrollPainter!.update(metrics, AxisDirection.down); diff --git a/packages/flutter/test/widgets/page_view_test.dart b/packages/flutter/test/widgets/page_view_test.dart index 37716e120d387..971422d838374 100644 --- a/packages/flutter/test/widgets/page_view_test.dart +++ b/packages/flutter/test/widgets/page_view_test.dart @@ -1048,6 +1048,7 @@ void main() { viewportDimension: 25.0, axisDirection: AxisDirection.right, viewportFraction: 1.0, + devicePixelRatio: tester.binding.window.devicePixelRatio, ); expect(page.page, 6); final PageMetrics page2 = page.copyWith( diff --git a/packages/flutter/test/widgets/scroll_physics_test.dart b/packages/flutter/test/widgets/scroll_physics_test.dart index 91c42aa937e6a..e24f2e8db2528 100644 --- a/packages/flutter/test/widgets/scroll_physics_test.dart +++ b/packages/flutter/test/widgets/scroll_physics_test.dart @@ -107,6 +107,7 @@ void main() { pixels: 20.0, viewportDimension: 500.0, axisDirection: AxisDirection.down, + devicePixelRatio: 3.0, ); const BouncingScrollPhysics bounce = BouncingScrollPhysics(); @@ -129,11 +130,12 @@ void main() { test('overscroll is progressively harder', () { final ScrollMetrics lessOverscrolledPosition = FixedScrollMetrics( - minScrollExtent: 0.0, - maxScrollExtent: 1000.0, - pixels: -20.0, - viewportDimension: 100.0, - axisDirection: AxisDirection.down, + minScrollExtent: 0.0, + maxScrollExtent: 1000.0, + pixels: -20.0, + viewportDimension: 100.0, + axisDirection: AxisDirection.down, + devicePixelRatio: 3.0, ); final ScrollMetrics moreOverscrolledPosition = FixedScrollMetrics( @@ -142,6 +144,7 @@ void main() { pixels: -40.0, viewportDimension: 100.0, axisDirection: AxisDirection.down, + devicePixelRatio: 3.0, ); final double lessOverscrollApplied = @@ -170,6 +173,7 @@ void main() { pixels: -20.0, viewportDimension: 100.0, axisDirection: AxisDirection.down, + devicePixelRatio: 3.0, ); final double easingApplied = @@ -186,6 +190,7 @@ void main() { pixels: 300.0, viewportDimension: 100.0, axisDirection: AxisDirection.down, + devicePixelRatio: 3.0, ); expect( @@ -205,6 +210,7 @@ void main() { pixels: -20.0, viewportDimension: 100.0, axisDirection: AxisDirection.down, + devicePixelRatio: 3.0, ); final double easingApplied = @@ -217,11 +223,12 @@ void main() { test('overscroll a small list and a big list works the same way', () { final ScrollMetrics smallListOverscrolledPosition = FixedScrollMetrics( - minScrollExtent: 0.0, - maxScrollExtent: 10.0, - pixels: -20.0, - viewportDimension: 100.0, - axisDirection: AxisDirection.down, + minScrollExtent: 0.0, + maxScrollExtent: 10.0, + pixels: -20.0, + viewportDimension: 100.0, + axisDirection: AxisDirection.down, + devicePixelRatio: 3.0, ); final ScrollMetrics bigListOverscrolledPosition = FixedScrollMetrics( @@ -230,6 +237,7 @@ void main() { pixels: -20.0, viewportDimension: 100.0, axisDirection: AxisDirection.down, + devicePixelRatio: 3.0, ); final double smallListOverscrollApplied = @@ -254,6 +262,7 @@ void main() { maxScrollExtent: 1000, viewportDimension: 0, axisDirection: AxisDirection.down, + devicePixelRatio: 3.0, ); expect(position.pixels, pixels); late FlutterError error; diff --git a/packages/flutter/test/widgets/scrollable_animations_test.dart b/packages/flutter/test/widgets/scrollable_animations_test.dart index 117fceeaeb466..b055f6a635fe8 100644 --- a/packages/flutter/test/widgets/scrollable_animations_test.dart +++ b/packages/flutter/test/widgets/scrollable_animations_test.dart @@ -41,7 +41,7 @@ void main() { expectNoAnimation(); - final double halfTolerance = controller.position.physics.tolerance.distance / 2; + final double halfTolerance = controller.position.physics.toleranceFor(controller.position).distance / 2; expect(halfTolerance, isNonZero); final double targetPosition = controller.position.pixels + halfTolerance; controller.position.animateTo(targetPosition, duration: const Duration(seconds: 10), curve: Curves.linear); @@ -64,7 +64,7 @@ void main() { expectNoAnimation(); - final double doubleTolerance = controller.position.physics.tolerance.distance * 2; + final double doubleTolerance = controller.position.physics.toleranceFor(controller.position).distance * 2; expect(doubleTolerance, isNonZero); final double targetPosition = controller.position.pixels + doubleTolerance; controller.position.animateTo(targetPosition, duration: const Duration(seconds: 10), curve: Curves.linear); diff --git a/packages/flutter/test/widgets/scrollbar_test.dart b/packages/flutter/test/widgets/scrollbar_test.dart index 0d4881afb17be..5e020cf646402 100644 --- a/packages/flutter/test/widgets/scrollbar_test.dart +++ b/packages/flutter/test/widgets/scrollbar_test.dart @@ -83,6 +83,7 @@ void main() { pixels: 0, viewportDimension: 100, axisDirection: AxisDirection.down, + devicePixelRatio: 3.0, ); test( diff --git a/packages/flutter/test/widgets/slivers_evil_test.dart b/packages/flutter/test/widgets/slivers_evil_test.dart index cf88354494a38..2c9aa0ef35366 100644 --- a/packages/flutter/test/widgets/slivers_evil_test.dart +++ b/packages/flutter/test/widgets/slivers_evil_test.dart @@ -53,7 +53,7 @@ class TestScrollPhysics extends ClampingScrollPhysics { } @override - Tolerance get tolerance => const Tolerance(velocity: 20.0, distance: 1.0); + Tolerance toleranceFor(ScrollMetrics metrics) => const Tolerance(velocity: 20.0, distance: 1.0); } void main() { From 6b9f1c228a431a1db3f9c7f8f5464d3589570b15 Mon Sep 17 00:00:00 2001 From: Greg Price Date: Tue, 3 Jan 2023 14:26:23 -0800 Subject: [PATCH 08/18] Minor documentation fix on BorderRadiusDirectional.zero (#117661) --- packages/flutter/lib/src/painting/border_radius.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/painting/border_radius.dart b/packages/flutter/lib/src/painting/border_radius.dart index ced35c010738d..8abaf286c95e3 100644 --- a/packages/flutter/lib/src/painting/border_radius.dart +++ b/packages/flutter/lib/src/painting/border_radius.dart @@ -590,7 +590,7 @@ class BorderRadiusDirectional extends BorderRadiusGeometry { /// A border radius with all zero radii. /// - /// Consider using [EdgeInsets.zero] instead, since that object has the same + /// Consider using [BorderRadius.zero] instead, since that object has the same /// effect, but will be cheaper to [resolve]. static const BorderRadiusDirectional zero = BorderRadiusDirectional.all(Radius.zero); From 889e35b3f3dfad610a8d948be98471acf78387fa Mon Sep 17 00:00:00 2001 From: CicadaCinema <52425971+CicadaCinema@users.noreply.github.com> Date: Tue, 3 Jan 2023 22:28:07 +0000 Subject: [PATCH 09/18] fix typos (#117592) --- dev/conductor/core/dart_test.yaml | 2 +- .../templates/plugin_ffi/linux.tmpl/CMakeLists.txt.tmpl | 4 ++-- .../templates/plugin_ffi/windows.tmpl/CMakeLists.txt.tmpl | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dev/conductor/core/dart_test.yaml b/dev/conductor/core/dart_test.yaml index ea3283fe598f4..264cf319a4a89 100644 --- a/dev/conductor/core/dart_test.yaml +++ b/dev/conductor/core/dart_test.yaml @@ -1,6 +1,6 @@ # codesign_integration_test takes longer than the default timeout which is 30s # since it has to clone both the engine and framework repos, and that test is running # asynchronously. The async function is being awaited more than 30s which counts as inactivity -# The default timeout needs to be extended to accomodate codesign_integration_test +# The default timeout needs to be extended to accommodate codesign_integration_test timeout: 5m diff --git a/packages/flutter_tools/templates/plugin_ffi/linux.tmpl/CMakeLists.txt.tmpl b/packages/flutter_tools/templates/plugin_ffi/linux.tmpl/CMakeLists.txt.tmpl index a328c52e7c82b..6b8415f0cbb9a 100644 --- a/packages/flutter_tools/templates/plugin_ffi/linux.tmpl/CMakeLists.txt.tmpl +++ b/packages/flutter_tools/templates/plugin_ffi/linux.tmpl/CMakeLists.txt.tmpl @@ -8,7 +8,7 @@ set(PROJECT_NAME "{{projectName}}") project(${PROJECT_NAME} LANGUAGES CXX) # Invoke the build for native code shared with the other target platforms. -# This can be changed to accomodate different builds. +# This can be changed to accommodate different builds. add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared") # List of absolute paths to libraries that should be bundled with the plugin. @@ -16,7 +16,7 @@ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DI # external build triggered from this build file. set({{projectName}}_bundled_libraries # Defined in ../src/CMakeLists.txt. - # This can be changed to accomodate different builds. + # This can be changed to accommodate different builds. $ PARENT_SCOPE ) diff --git a/packages/flutter_tools/templates/plugin_ffi/windows.tmpl/CMakeLists.txt.tmpl b/packages/flutter_tools/templates/plugin_ffi/windows.tmpl/CMakeLists.txt.tmpl index cfd21c3676cac..36a7bd32511ed 100644 --- a/packages/flutter_tools/templates/plugin_ffi/windows.tmpl/CMakeLists.txt.tmpl +++ b/packages/flutter_tools/templates/plugin_ffi/windows.tmpl/CMakeLists.txt.tmpl @@ -9,7 +9,7 @@ set(PROJECT_NAME "{{projectName}}") project(${PROJECT_NAME} LANGUAGES CXX) # Invoke the build for native code shared with the other target platforms. -# This can be changed to accomodate different builds. +# This can be changed to accommodate different builds. add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DIR}/shared") # List of absolute paths to libraries that should be bundled with the plugin. @@ -17,7 +17,7 @@ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_BINARY_DI # external build triggered from this build file. set({{projectName}}_bundled_libraries # Defined in ../src/CMakeLists.txt. - # This can be changed to accomodate different builds. + # This can be changed to accommodate different builds. $ PARENT_SCOPE ) From bd69ef70aec5ec7122db107c6dcb48b69018fdcc Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 3 Jan 2023 17:32:06 -0500 Subject: [PATCH 10/18] c0b3f8fce Make `AccessibilityBridge` a `AXPlatformTreeManager` (flutter/engine#38610) (#117931) --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 752c8e6ca7ec7..59e348b39be23 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -1d2ba73d1059544564e644055d4366956e581e87 +c0b3f8fcea0fb8e0ab4cd4bb3205968381c51cc2 From a7942e80d487345b3ba0100a2d1179e49905c02e Mon Sep 17 00:00:00 2001 From: Tae Hyung Kim Date: Tue, 3 Jan 2023 14:33:55 -0800 Subject: [PATCH 11/18] Add convenience constructors for SliverList (#116605) * init * lint * add the other two slivers * fix lint * add test for sliverlist.separated * add3 more * fix lint and tests * remove trailing spaces * remove trailing spaces 2 * fix lint * fix lint again --- packages/flutter/lib/src/widgets/sliver.dart | 289 ++++++++++++++++++ .../widgets/sliver_prototype_extent_list.dart | 103 +++++++ .../sliver_prototype_item_extent_test.dart | 56 +++- .../flutter/test/widgets/slivers_test.dart | 258 ++++++++++++++++ 4 files changed, 705 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/widgets/sliver.dart b/packages/flutter/lib/src/widgets/sliver.dart index 5dce14a159007..c82ad2ca9de9c 100644 --- a/packages/flutter/lib/src/widgets/sliver.dart +++ b/packages/flutter/lib/src/widgets/sliver.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:collection' show HashMap, SplayTreeMap; +import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; @@ -1035,6 +1036,184 @@ class SliverList extends SliverMultiBoxAdaptorWidget { required super.delegate, }); + /// A sliver that places multiple box children in a linear array along the main + /// axis. + /// + /// This constructor is appropriate for sliver lists with a large (or + /// infinite) number of children because the builder is called only for those + /// children that are actually visible. + /// + /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] + /// to estimate the maximum scroll extent. + /// + /// `itemBuilder` will be called only with indices greater than or equal to + /// zero and less than `itemCount`. + /// + /// {@macro flutter.widgets.ListView.builder.itemBuilder} + /// + /// {@macro flutter.widgets.PageView.findChildIndexCallback} + /// + /// The `addAutomaticKeepAlives` argument corresponds to the + /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The + /// `addRepaintBoundaries` argument corresponds to the + /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The + /// `addSemanticIndexes` argument corresponds to the + /// [SliverChildBuilderDelegate.addSemanticIndexes] property. + /// + /// {@tool snippet} + /// This example, which would be inserted into a [CustomScrollView.slivers] + /// list, shows an infinite number of items in varying shades of blue: + /// + /// ```dart + /// SliverList.builder( + /// itemBuilder: (BuildContext context, int index) { + /// return Container( + /// alignment: Alignment.center, + /// color: Colors.lightBlue[100 * (index % 9)], + /// child: Text('list item $index'), + /// ); + /// }, + /// ) + /// ``` + /// {@end-tool} + SliverList.builder({ + super.key, + required NullableIndexedWidgetBuilder itemBuilder, + ChildIndexGetter? findChildIndexCallback, + int? itemCount, + bool addAutomaticKeepAlives = true, + bool addRepaintBoundaries = true, + bool addSemanticIndexes = true, + }) : super(delegate: SliverChildBuilderDelegate( + itemBuilder, + findChildIndexCallback: findChildIndexCallback, + childCount: itemCount, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + )); + + /// A sliver that places multiple box children, separated by box widgets, in a linear array along the main + /// axis. + /// + /// This constructor is appropriate for sliver lists with a large (or + /// infinite) number of children because the builder is called only for those + /// children that are actually visible. + /// + /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] + /// to estimate the maximum scroll extent. + /// + /// `itemBuilder` will be called only with indices greater than or equal to + /// zero and less than `itemCount`. + /// + /// {@macro flutter.widgets.ListView.builder.itemBuilder} + /// + /// {@macro flutter.widgets.PageView.findChildIndexCallback} + /// + /// + /// The `separatorBuilder` is similar to `itemBuilder`, except it is the widget + /// that gets placed between itemBuilder(context, index) and itemBuilder(context, index + 1). + /// + /// The `addAutomaticKeepAlives` argument corresponds to the + /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The + /// `addRepaintBoundaries` argument corresponds to the + /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The + /// `addSemanticIndexes` argument corresponds to the + /// [SliverChildBuilderDelegate.addSemanticIndexes] property. + /// {@tool snippet} + /// + /// This example shows how to create a [SliverList] whose [Container] items + /// are separated by [Divider]s. + /// + /// ```dart + /// SliverList.separated( + /// itemBuilder: (BuildContext context, int index) { + /// return Container( + /// alignment: Alignment.center, + /// color: Colors.lightBlue[100 * (index % 9)], + /// child: Text('list item $index'), + /// ); + /// }, + /// separatorBuilder: (BuildContext context, int index) => const Divider(), + /// ) + /// ``` + /// {@end-tool} + SliverList.separated({ + super.key, + required NullableIndexedWidgetBuilder itemBuilder, + ChildIndexGetter? findChildIndexCallback, + required NullableIndexedWidgetBuilder separatorBuilder, + int? itemCount, + bool addAutomaticKeepAlives = true, + bool addRepaintBoundaries = true, + bool addSemanticIndexes = true, + }) : assert(itemBuilder != null), + assert(separatorBuilder != null), + super(delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + final int itemIndex = index ~/ 2; + final Widget? widget; + if (index.isEven) { + widget = itemBuilder(context, itemIndex); + } else { + widget = separatorBuilder(context, itemIndex); + assert(() { + if (widget == null) { + throw FlutterError('separatorBuilder cannot return null.'); + } + return true; + }()); + } + return widget; + }, + findChildIndexCallback: findChildIndexCallback, + childCount: itemCount == null ? null : math.max(0, itemCount * 2 - 1), + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + semanticIndexCallback: (Widget _, int index) { + return index.isEven ? index ~/ 2 : null; + }, + )); + + /// A sliver that places multiple box children in a linear array along the main + /// axis. + /// + /// This constructor uses a list of [Widget]s to build the sliver. + /// + /// The `addAutomaticKeepAlives` argument corresponds to the + /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The + /// `addRepaintBoundaries` argument corresponds to the + /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The + /// `addSemanticIndexes` argument corresponds to the + /// [SliverChildBuilderDelegate.addSemanticIndexes] property. + /// + /// {@tool snippet} + /// This example, which would be inserted into a [CustomScrollView.slivers] + /// list, shows an infinite number of items in varying shades of blue: + /// + /// ```dart + /// SliverList.list( + /// children: const [ + /// Text('Hello'), + /// Text('World!'), + /// ], + /// ); + /// ``` + /// {@end-tool} + SliverList.list({ + super.key, + required List children, + bool addAutomaticKeepAlives = true, + bool addRepaintBoundaries = true, + bool addSemanticIndexes = true, + }) : super(delegate: SliverChildListDelegate( + children, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + )); + @override SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this, replaceMovedChildren: true); @@ -1098,6 +1277,116 @@ class SliverFixedExtentList extends SliverMultiBoxAdaptorWidget { required this.itemExtent, }); + /// A sliver that places multiple box children in a linear array along the main + /// axis. + /// + /// [SliverFixedExtentList] places its children in a linear array along the main + /// axis starting at offset zero and without gaps. Each child is forced to have + /// the [itemExtent] in the main axis and the + /// [SliverConstraints.crossAxisExtent] in the cross axis. + /// + /// This constructor is appropriate for sliver lists with a large (or + /// infinite) number of children whose extent is already determined. + /// + /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] + /// to estimate the maximum scroll extent. + /// + /// `itemBuilder` will be called only with indices greater than or equal to + /// zero and less than `itemCount`. + /// + /// {@macro flutter.widgets.ListView.builder.itemBuilder} + /// + /// The `itemExtent` argument is the extent of each item. + /// + /// {@macro flutter.widgets.PageView.findChildIndexCallback} + /// + /// The `addAutomaticKeepAlives` argument corresponds to the + /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The + /// `addRepaintBoundaries` argument corresponds to the + /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The + /// `addSemanticIndexes` argument corresponds to the + /// [SliverChildBuilderDelegate.addSemanticIndexes] property. + /// {@tool snippet} + /// + /// This example, which would be inserted into a [CustomScrollView.slivers] + /// list, shows an infinite number of items in varying shades of blue: + /// + /// ```dart + /// SliverFixedExtentList.builder( + /// itemExtent: 50.0, + /// itemBuilder: (BuildContext context, int index) { + /// return Container( + /// alignment: Alignment.center, + /// color: Colors.lightBlue[100 * (index % 9)], + /// child: Text('list item $index'), + /// ); + /// }, + /// ) + /// ``` + /// {@end-tool} + SliverFixedExtentList.builder({ + super.key, + required NullableIndexedWidgetBuilder itemBuilder, + required this.itemExtent, + ChildIndexGetter? findChildIndexCallback, + int? itemCount, + bool addAutomaticKeepAlives = true, + bool addRepaintBoundaries = true, + bool addSemanticIndexes = true, + }) : super(delegate: SliverChildBuilderDelegate( + itemBuilder, + findChildIndexCallback: findChildIndexCallback, + childCount: itemCount, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + )); + + /// A sliver that places multiple box children in a linear array along the main + /// axis. + /// + /// [SliverFixedExtentList] places its children in a linear array along the main + /// axis starting at offset zero and without gaps. Each child is forced to have + /// the [itemExtent] in the main axis and the + /// [SliverConstraints.crossAxisExtent] in the cross axis. + /// + /// This constructor uses a list of [Widget]s to build the sliver. + /// + /// The `addAutomaticKeepAlives` argument corresponds to the + /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The + /// `addRepaintBoundaries` argument corresponds to the + /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The + /// `addSemanticIndexes` argument corresponds to the + /// [SliverChildBuilderDelegate.addSemanticIndexes] property. + /// + /// {@tool snippet} + /// This example, which would be inserted into a [CustomScrollView.slivers] + /// list, shows an infinite number of items in varying shades of blue: + /// + /// ```dart + /// SliverFixedExtentList.list( + /// itemExtent: 50.0, + /// children: const [ + /// Text('Hello'), + /// Text('World!'), + /// ], + /// ); + /// ``` + /// {@end-tool} + SliverFixedExtentList.list({ + super.key, + required List children, + required this.itemExtent, + bool addAutomaticKeepAlives = true, + bool addRepaintBoundaries = true, + bool addSemanticIndexes = true, + }) : super(delegate: SliverChildListDelegate( + children, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + )); + /// The extent the children are forced to have in the main axis. final double itemExtent; diff --git a/packages/flutter/lib/src/widgets/sliver_prototype_extent_list.dart b/packages/flutter/lib/src/widgets/sliver_prototype_extent_list.dart index 5fa4533b2c22b..93826086b7b8b 100644 --- a/packages/flutter/lib/src/widgets/sliver_prototype_extent_list.dart +++ b/packages/flutter/lib/src/widgets/sliver_prototype_extent_list.dart @@ -40,6 +40,109 @@ class SliverPrototypeExtentList extends SliverMultiBoxAdaptorWidget { required this.prototypeItem, }) : assert(prototypeItem != null); + /// A sliver that places its box children in a linear array and constrains them + /// to have the same extent as a prototype item along the main axis. + /// + /// This constructor is appropriate for sliver lists with a large (or + /// infinite) number of children whose extent is already determined. + /// + /// Providing a non-null `itemCount` improves the ability of the [SliverGrid] + /// to estimate the maximum scroll extent. + /// + /// `itemBuilder` will be called only with indices greater than or equal to + /// zero and less than `itemCount`. + /// + /// {@macro flutter.widgets.ListView.builder.itemBuilder} + /// + /// The `prototypeItem` argument is used to determine the extent of each item. + /// + /// {@macro flutter.widgets.PageView.findChildIndexCallback} + /// + /// The `addAutomaticKeepAlives` argument corresponds to the + /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The + /// `addRepaintBoundaries` argument corresponds to the + /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The + /// `addSemanticIndexes` argument corresponds to the + /// [SliverChildBuilderDelegate.addSemanticIndexes] property. + /// + /// {@tool snippet} + /// This example, which would be inserted into a [CustomScrollView.slivers] + /// list, shows an infinite number of items in varying shades of blue: + /// + /// ```dart + /// SliverPrototypeExtentList.builder( + /// prototypeItem: Container( + /// alignment: Alignment.center, + /// child: const Text('list item prototype'), + /// ), + /// itemBuilder: (BuildContext context, int index) { + /// return Container( + /// alignment: Alignment.center, + /// color: Colors.lightBlue[100 * (index % 9)], + /// child: Text('list item $index'), + /// ); + /// }, + /// ) + /// ``` + /// {@end-tool} + SliverPrototypeExtentList.builder({ + super.key, + required NullableIndexedWidgetBuilder itemBuilder, + required this.prototypeItem, + ChildIndexGetter? findChildIndexCallback, + int? itemCount, + bool addAutomaticKeepAlives = true, + bool addRepaintBoundaries = true, + bool addSemanticIndexes = true, + }) : super(delegate: SliverChildBuilderDelegate( + itemBuilder, + findChildIndexCallback: findChildIndexCallback, + childCount: itemCount, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + )); + + /// A sliver that places multiple box children in a linear array along the main + /// axis. + /// + /// This constructor uses a list of [Widget]s to build the sliver. + /// + /// The `addAutomaticKeepAlives` argument corresponds to the + /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The + /// `addRepaintBoundaries` argument corresponds to the + /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The + /// `addSemanticIndexes` argument corresponds to the + /// [SliverChildBuilderDelegate.addSemanticIndexes] property. + /// + /// {@tool snippet} + /// This example, which would be inserted into a [CustomScrollView.slivers] + /// list, shows an infinite number of items in varying shades of blue: + /// + /// ```dart + /// SliverPrototypeExtentList.list( + /// prototypeItem: const Text('Hello'), + /// children: const [ + /// Text('Hello'), + /// Text('World!'), + /// ], + /// ); + /// ``` + /// {@end-tool} + SliverPrototypeExtentList.list({ + super.key, + required List children, + required this.prototypeItem, + bool addAutomaticKeepAlives = true, + bool addRepaintBoundaries = true, + bool addSemanticIndexes = true, + }) : super(delegate: SliverChildListDelegate( + children, + addAutomaticKeepAlives: addAutomaticKeepAlives, + addRepaintBoundaries: addRepaintBoundaries, + addSemanticIndexes: addSemanticIndexes, + )); + /// Defines the main axis extent of all of this sliver's children. /// /// The [prototypeItem] is laid out before the rest of the sliver's children diff --git a/packages/flutter/test/widgets/sliver_prototype_item_extent_test.dart b/packages/flutter/test/widgets/sliver_prototype_item_extent_test.dart index 247df56c4cac3..48b000f5eb477 100644 --- a/packages/flutter/test/widgets/sliver_prototype_item_extent_test.dart +++ b/packages/flutter/test/widgets/sliver_prototype_item_extent_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; class TestItem extends StatelessWidget { @@ -41,6 +40,61 @@ Widget buildFrame({ int? count, double? width, double? height, Axis? scrollDirec } void main() { + testWidgets('SliverPrototypeExtentList.builder test', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CustomScrollView( + slivers: [ + SliverPrototypeExtentList.builder( + itemBuilder: (BuildContext context, int index) => TestItem(item: index), + prototypeItem: const TestItem(item: -1, height: 100.0), + itemCount: 20, + ), + ], + ), + ), + ), + ); + + // The viewport is 600 pixels high, lazily created items are 100 pixels high. + for (int i = 0; i < 6; i += 1) { + final Finder item = find.widgetWithText(Container, 'Item $i'); + expect(item, findsOneWidget); + expect(tester.getTopLeft(item).dy, i * 100.0); + expect(tester.getSize(item).height, 100.0); + } + for (int i = 7; i < 20; i += 1) { + expect(find.text('Item $i'), findsNothing); + } + }); + + testWidgets('SliverPrototypeExtentList.builder test', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CustomScrollView( + slivers: [ + SliverPrototypeExtentList.list( + prototypeItem: const TestItem(item: -1, height: 100.0), + children: [0, 1, 2, 3, 4, 5, 6, 7].map((int index) => TestItem(item: index)).toList(), + ), + ], + ), + ), + ), + ); + + // The viewport is 600 pixels high, lazily created items are 100 pixels high. + for (int i = 0; i < 6; i += 1) { + final Finder item = find.widgetWithText(Container, 'Item $i'); + expect(item, findsOneWidget); + expect(tester.getTopLeft(item).dy, i * 100.0); + expect(tester.getSize(item).height, 100.0); + } + expect(find.text('Item 7'), findsNothing); + }); + testWidgets('SliverPrototypeExtentList vertical scrolling basics', (WidgetTester tester) async { await tester.pumpWidget(buildFrame(count: 20, height: 100.0)); diff --git a/packages/flutter/test/widgets/slivers_test.dart b/packages/flutter/test/widgets/slivers_test.dart index 0067cc409a9e0..bd9231afd2ccd 100644 --- a/packages/flutter/test/widgets/slivers_test.dart +++ b/packages/flutter/test/widgets/slivers_test.dart @@ -992,6 +992,264 @@ void main() { expect(secondTapped, 1); }); + testWidgets('SliverList.builder can build children', (WidgetTester tester) async { + int firstTapped = 0; + int secondTapped = 0; + final Key key = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + key: key, + body: CustomScrollView( + slivers: [ + SliverList.builder( + itemCount: 2, + itemBuilder: (BuildContext context, int index) { + return Material( + color: index.isEven ? Colors.yellow : Colors.red, + child: InkWell( + onTap: () { + index.isEven ? firstTapped++ : secondTapped++; + }, + child: Text('Index $index'), + ), + ); + }, + ), + ], + ), + ), + )); + + // Verify correct hit testing + await tester.tap(find.text('Index 0')); + expect(firstTapped, 1); + expect(secondTapped, 0); + firstTapped = 0; + await tester.tap(find.text('Index 1')); + expect(firstTapped, 0); + expect(secondTapped, 1); + }); + + testWidgets('SliverList.builder can build children', (WidgetTester tester) async { + int firstTapped = 0; + int secondTapped = 0; + final Key key = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + key: key, + body: CustomScrollView( + slivers: [ + SliverList.builder( + itemCount: 2, + itemBuilder: (BuildContext context, int index) { + return Material( + color: index.isEven ? Colors.yellow : Colors.red, + child: InkWell( + onTap: () { + index.isEven ? firstTapped++ : secondTapped++; + }, + child: Text('Index $index'), + ), + ); + }, + ), + ], + ), + ), + )); + + // Verify correct hit testing + await tester.tap(find.text('Index 0')); + expect(firstTapped, 1); + expect(secondTapped, 0); + firstTapped = 0; + await tester.tap(find.text('Index 1')); + expect(firstTapped, 0); + expect(secondTapped, 1); + }); + + testWidgets('SliverList.separated can build children', (WidgetTester tester) async { + int firstTapped = 0; + int secondTapped = 0; + final Key key = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + key: key, + body: CustomScrollView( + slivers: [ + SliverList.separated( + itemCount: 2, + itemBuilder: (BuildContext context, int index) { + return Material( + color: index.isEven ? Colors.yellow : Colors.red, + child: InkWell( + onTap: () { + index.isEven ? firstTapped++ : secondTapped++; + }, + child: Text('Index $index'), + ), + ); + }, + separatorBuilder: (BuildContext context, int index) => Text('Separator $index'), + ), + ], + ), + ), + )); + + // Verify correct hit testing + await tester.tap(find.text('Index 0')); + expect(firstTapped, 1); + expect(secondTapped, 0); + firstTapped = 0; + await tester.tap(find.text('Index 1')); + expect(firstTapped, 0); + expect(secondTapped, 1); + }); + + testWidgets('SliverList.separated has correct number of children', (WidgetTester tester) async { + final Key key = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + key: key, + body: CustomScrollView( + slivers: [ + SliverList.separated( + itemCount: 2, + itemBuilder: (BuildContext context, int index) => const Text('item'), + separatorBuilder: (BuildContext context, int index) => const Text('separator'), + ), + ], + ), + ), + )); + expect(find.text('item'), findsNWidgets(2)); + expect(find.text('separator'), findsNWidgets(1)); + }); + + testWidgets('SliverList.list can build children', (WidgetTester tester) async { + int firstTapped = 0; + int secondTapped = 0; + final Key key = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + key: key, + body: CustomScrollView( + slivers: [ + SliverList.list( + children: [ + Material( + color: Colors.yellow, + child: InkWell( + onTap: () => firstTapped++, + child: const Text('Index 0'), + ), + ), + Material( + color: Colors.red, + child: InkWell( + onTap: () => secondTapped++, + child: const Text('Index 1'), + ), + ), + ], + ), + ], + ), + ), + )); + + // Verify correct hit testing + await tester.tap(find.text('Index 0')); + expect(firstTapped, 1); + expect(secondTapped, 0); + firstTapped = 0; + await tester.tap(find.text('Index 1')); + expect(firstTapped, 0); + expect(secondTapped, 1); + }); + + testWidgets('SliverFixedExtentList.builder can build children', (WidgetTester tester) async { + int firstTapped = 0; + int secondTapped = 0; + final Key key = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + key: key, + body: CustomScrollView( + slivers: [ + SliverFixedExtentList.builder( + itemCount: 2, + itemExtent: 100, + itemBuilder: (BuildContext context, int index) { + return Material( + color: index.isEven ? Colors.yellow : Colors.red, + child: InkWell( + onTap: () { + index.isEven ? firstTapped++ : secondTapped++; + }, + child: Text('Index $index'), + ), + ); + }, + ), + ], + ), + ), + )); + // Verify correct hit testing + await tester.tap(find.text('Index 0')); + expect(firstTapped, 1); + expect(secondTapped, 0); + firstTapped = 0; + await tester.tap(find.text('Index 1')); + expect(firstTapped, 0); + expect(secondTapped, 1); + }); + + testWidgets('SliverList.list can build children', (WidgetTester tester) async { + int firstTapped = 0; + int secondTapped = 0; + final Key key = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + key: key, + body: CustomScrollView( + slivers: [ + SliverFixedExtentList.list( + itemExtent: 100, + children: [ + Material( + color: Colors.yellow, + child: InkWell( + onTap: () => firstTapped++, + child: const Text('Index 0'), + ), + ), + Material( + color: Colors.red, + child: InkWell( + onTap: () => secondTapped++, + child: const Text('Index 1'), + ), + ), + ], + ), + ], + ), + ), + )); + + // Verify correct hit testing + await tester.tap(find.text('Index 0')); + expect(firstTapped, 1); + expect(secondTapped, 0); + firstTapped = 0; + await tester.tap(find.text('Index 1')); + expect(firstTapped, 0); + expect(secondTapped, 1); + }); + testWidgets('SliverGrid.builder can build children', (WidgetTester tester) async { int firstTapped = 0; int secondTapped = 0; From dbd36fb1331abb7417b2047a49d52e88e5460f33 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 3 Jan 2023 18:15:05 -0500 Subject: [PATCH 12/18] 2213b80dd [Impeller Scene] Use std::chrono for animation durations (flutter/engine#38606) (#117935) --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 59e348b39be23..307c25f9942b2 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -c0b3f8fcea0fb8e0ab4cd4bb3205968381c51cc2 +2213b80dd964f09ed087b4b284490af7b7e4e82a From 9080d1acc58f3d45ae3f4a94c9ab231236aa843d Mon Sep 17 00:00:00 2001 From: Renzo Olivares Date: Tue, 3 Jan 2023 16:54:10 -0800 Subject: [PATCH 13/18] Reland "Add support for double tap and drag for text selection #109573" (#117502) * Revert "Revert "Add support for double tap and drag for text selection (#109573)" (#117497)" This reverts commit 39fa0117a919bd401c4c8734c97ddb46fbc45cb7. * Allow TapAndDragGestureRecognizer to accept pointer events from any devices -- the TapGestureRecognizer it replaces was previously doing this Co-authored-by: Renzo Olivares --- .../flutter/lib/src/cupertino/text_field.dart | 4 +- .../lib/src/gestures/drag_details.dart | 6 +- .../flutter/lib/src/gestures/monodrag.dart | 8 + packages/flutter/lib/src/gestures/tap.dart | 18 + .../lib/src/material/selectable_text.dart | 2 +- .../flutter/lib/src/material/text_field.dart | 2 +- .../src/widgets/tap_and_drag_gestures.dart | 1288 +++++++++++++++++ .../lib/src/widgets/text_selection.dart | 430 +++--- packages/flutter/lib/widgets.dart | 1 + .../test/cupertino/text_field_test.dart | 223 ++- packages/flutter/test/gestures/tap_test.dart | 1 - .../test/material/text_field_test.dart | 259 +++- .../test/widgets/selectable_text_test.dart | 4 +- .../widgets/tap_and_drag_gestures_test.dart | 552 +++++++ .../test/widgets/text_selection_test.dart | 52 +- 15 files changed, 2635 insertions(+), 215 deletions(-) create mode 100644 packages/flutter/lib/src/widgets/tap_and_drag_gestures.dart create mode 100644 packages/flutter/test/widgets/tap_and_drag_gestures_test.dart diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart index 977b9df22ecd8..72508e3d02b43 100644 --- a/packages/flutter/lib/src/cupertino/text_field.dart +++ b/packages/flutter/lib/src/cupertino/text_field.dart @@ -102,7 +102,7 @@ class _CupertinoTextFieldSelectionGestureDetectorBuilder extends TextSelectionGe final _CupertinoTextFieldState _state; @override - void onSingleTapUp(TapUpDetails details) { + void onSingleTapUp(TapDragUpDetails details) { // Because TextSelectionGestureDetector listens to taps that happen on // widgets in front of it, tapping the clear button will also trigger // this handler. If the clear button widget recognizes the up event, @@ -120,7 +120,7 @@ class _CupertinoTextFieldSelectionGestureDetectorBuilder extends TextSelectionGe } @override - void onDragSelectionEnd(DragEndDetails details) { + void onDragSelectionEnd(TapDragEndDetails details) { _state._requestKeyboard(); } } diff --git a/packages/flutter/lib/src/gestures/drag_details.dart b/packages/flutter/lib/src/gestures/drag_details.dart index 40308accb8f31..0053f20d414f5 100644 --- a/packages/flutter/lib/src/gestures/drag_details.dart +++ b/packages/flutter/lib/src/gestures/drag_details.dart @@ -109,10 +109,12 @@ class DragStartDetails { String toString() => '${objectRuntimeType(this, 'DragStartDetails')}($globalPosition)'; } +/// {@template flutter.gestures.dragdetails.GestureDragStartCallback} /// Signature for when a pointer has contacted the screen and has begun to move. /// /// The `details` object provides the position of the touch when it first /// touched the surface. +/// {@endtemplate} /// /// See [DragGestureRecognizer.onStart]. typedef GestureDragStartCallback = void Function(DragStartDetails details); @@ -126,7 +128,7 @@ typedef GestureDragStartCallback = void Function(DragStartDetails details); /// * [DragStartDetails], the details for [GestureDragStartCallback]. /// * [DragEndDetails], the details for [GestureDragEndCallback]. class DragUpdateDetails { - /// Creates details for a [DragUpdateDetails]. + /// Creates details for a [GestureDragUpdateCallback]. /// /// The [delta] argument must not be null. /// @@ -195,11 +197,13 @@ class DragUpdateDetails { String toString() => '${objectRuntimeType(this, 'DragUpdateDetails')}($delta)'; } +/// {@template flutter.gestures.dragdetails.GestureDragUpdateCallback} /// Signature for when a pointer that is in contact with the screen and moving /// has moved again. /// /// The `details` object provides the position of the touch and the distance it /// has traveled since the last update. +/// {@endtemplate} /// /// See [DragGestureRecognizer.onUpdate]. typedef GestureDragUpdateCallback = void Function(DragUpdateDetails details); diff --git a/packages/flutter/lib/src/gestures/monodrag.dart b/packages/flutter/lib/src/gestures/monodrag.dart index 15e6d5e9d6e82..500d296b20536 100644 --- a/packages/flutter/lib/src/gestures/monodrag.dart +++ b/packages/flutter/lib/src/gestures/monodrag.dart @@ -26,11 +26,13 @@ enum _DragState { accepted, } +/// {@template flutter.gestures.monodrag.GestureDragEndCallback} /// Signature for when a pointer that was previously in contact with the screen /// and moving is no longer in contact with the screen. /// /// The velocity at which the pointer was moving when it stopped contacting /// the screen is available in the `details`. +/// {@endtemplate} /// /// Used by [DragGestureRecognizer.onEnd]. typedef GestureDragEndCallback = void Function(DragEndDetails details); @@ -124,8 +126,10 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { /// * [DragDownDetails], which is passed as an argument to this callback. GestureDragDownCallback? onDown; + /// {@template flutter.gestures.monodrag.DragGestureRecognizer.onStart} /// A pointer has contacted the screen with a primary button and has begun to /// move. + /// {@endtemplate} /// /// The position of the pointer is provided in the callback's `details` /// argument, which is a [DragStartDetails] object. The [dragStartBehavior] @@ -137,8 +141,10 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { /// * [DragStartDetails], which is passed as an argument to this callback. GestureDragStartCallback? onStart; + /// {@template flutter.gestures.monodrag.DragGestureRecognizer.onUpdate} /// A pointer that is in contact with the screen with a primary button and /// moving has moved again. + /// {@endtemplate} /// /// The distance traveled by the pointer since the last update is provided in /// the callback's `details` argument, which is a [DragUpdateDetails] object. @@ -149,9 +155,11 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { /// * [DragUpdateDetails], which is passed as an argument to this callback. GestureDragUpdateCallback? onUpdate; + /// {@template flutter.gestures.monodrag.DragGestureRecognizer.onEnd} /// A pointer that was previously in contact with the screen with a primary /// button and moving is no longer in contact with the screen and was moving /// at a specific velocity when it stopped contacting the screen. + /// {@endtemplate} /// /// The velocity is provided in the callback's `details` argument, which is a /// [DragEndDetails] object. diff --git a/packages/flutter/lib/src/gestures/tap.dart b/packages/flutter/lib/src/gestures/tap.dart index a05d839cfaee0..c2ce8aecda507 100644 --- a/packages/flutter/lib/src/gestures/tap.dart +++ b/packages/flutter/lib/src/gestures/tap.dart @@ -45,11 +45,13 @@ class TapDownDetails { final Offset localPosition; } +/// {@template flutter.gestures.tap.GestureTapDownCallback} /// Signature for when a pointer that might cause a tap has contacted the /// screen. /// /// The position at which the pointer contacted the screen is available in the /// `details`. +/// {@endtemplate} /// /// See also: /// @@ -82,11 +84,13 @@ class TapUpDetails { final PointerDeviceKind kind; } +/// {@template flutter.gestures.tap.GestureTapUpCallback} /// Signature for when a pointer that will trigger a tap has stopped contacting /// the screen. /// /// The position at which the pointer stopped contacting the screen is available /// in the `details`. +/// {@endtemplate} /// /// See also: /// @@ -360,8 +364,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { /// {@macro flutter.gestures.GestureRecognizer.supportedDevices} TapGestureRecognizer({ super.debugOwner, super.supportedDevices }); + /// {@template flutter.gestures.tap.TapGestureRecognizer.onTapDown} /// A pointer has contacted the screen at a particular location with a primary /// button, which might be the start of a tap. + /// {@endtemplate} /// /// This triggers after the down event, once a short timeout ([deadline]) has /// elapsed, or once the gestures has won the arena, whichever comes first. @@ -378,8 +384,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { /// * [GestureDetector.onTapDown], which exposes this callback. GestureTapDownCallback? onTapDown; + /// {@template flutter.gestures.tap.TapGestureRecognizer.onTapUp} /// A pointer has stopped contacting the screen at a particular location, /// which is recognized as a tap of a primary button. + /// {@endtemplate} /// /// This triggers on the up event, if the recognizer wins the arena with it /// or has previously won, immediately followed by [onTap]. @@ -411,8 +419,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { /// * [GestureDetector.onTap], which exposes this callback. GestureTapCallback? onTap; + /// {@template flutter.gestures.tap.TapGestureRecognizer.onTapCancel} /// A pointer that previously triggered [onTapDown] will not end up causing /// a tap. + /// {@endtemplate} /// /// This triggers once the gesture loses the arena if [onTapDown] has /// previously been triggered. @@ -428,8 +438,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { /// * [GestureDetector.onTapCancel], which exposes this callback. GestureTapCancelCallback? onTapCancel; + /// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTap} /// A pointer has stopped contacting the screen, which is recognized as a tap /// of a secondary button. + /// {@endtemplate} /// /// This triggers on the up event, if the recognizer wins the arena with it or /// has previously won, immediately following [onSecondaryTapUp]. @@ -444,8 +456,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { /// * [GestureDetector.onSecondaryTap], which exposes this callback. GestureTapCallback? onSecondaryTap; + /// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTapDown} /// A pointer has contacted the screen at a particular location with a /// secondary button, which might be the start of a secondary tap. + /// {@endtemplate} /// /// This triggers after the down event, once a short timeout ([deadline]) has /// elapsed, or once the gestures has won the arena, whichever comes first. @@ -462,8 +476,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { /// * [GestureDetector.onSecondaryTapDown], which exposes this callback. GestureTapDownCallback? onSecondaryTapDown; + /// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTapUp} /// A pointer has stopped contacting the screen at a particular location, /// which is recognized as a tap of a secondary button. + /// {@endtemplate} /// /// This triggers on the up event if the recognizer wins the arena with it /// or has previously won. @@ -482,8 +498,10 @@ class TapGestureRecognizer extends BaseTapGestureRecognizer { /// * [GestureDetector.onSecondaryTapUp], which exposes this callback. GestureTapUpCallback? onSecondaryTapUp; + /// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTapCancel} /// A pointer that previously triggered [onSecondaryTapDown] will not end up /// causing a tap. + /// {@endtemplate} /// /// This triggers once the gesture loses the arena if [onSecondaryTapDown] /// has previously been triggered. diff --git a/packages/flutter/lib/src/material/selectable_text.dart b/packages/flutter/lib/src/material/selectable_text.dart index aeb5a3a367112..226623a83092b 100644 --- a/packages/flutter/lib/src/material/selectable_text.dart +++ b/packages/flutter/lib/src/material/selectable_text.dart @@ -85,7 +85,7 @@ class _SelectableTextSelectionGestureDetectorBuilder extends TextSelectionGestur } @override - void onSingleTapUp(TapUpDetails details) { + void onSingleTapUp(TapDragUpDetails details) { editableText.hideToolbar(); if (delegate.selectionEnabled) { switch (Theme.of(_state.context).platform) { diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index d4f0eaf1ca0a0..d740e46add0f8 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -65,7 +65,7 @@ class _TextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDete } @override - void onSingleTapUp(TapUpDetails details) { + void onSingleTapUp(TapDragUpDetails details) { super.onSingleTapUp(details); _state._requestKeyboard(); _state.widget.onTap?.call(); diff --git a/packages/flutter/lib/src/widgets/tap_and_drag_gestures.dart b/packages/flutter/lib/src/widgets/tap_and_drag_gestures.dart new file mode 100644 index 0000000000000..e56f7ca074b66 --- /dev/null +++ b/packages/flutter/lib/src/widgets/tap_and_drag_gestures.dart @@ -0,0 +1,1288 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart' show HardwareKeyboard, LogicalKeyboardKey; + +double _getGlobalDistance(PointerEvent event, OffsetPair? originPosition) { + assert(originPosition != null); + final Offset offset = event.position - originPosition!.global; + return offset.distance; +} + +// The possible states of a [TapAndDragGestureRecognizer]. +// +// The recognizer advances from [ready] to [possible] when it starts tracking +// a pointer in [TapAndDragGestureRecognizer.addAllowedPointer]. Where it advances +// from there depends on the sequence of pointer events that is tracked by the +// recognizer, following the initial [PointerDownEvent]: +// +// * If a [PointerUpEvent] has not been tracked, the recognizer stays in the [possible] +// state as long as it continues to track a pointer. +// * If a [PointerMoveEvent] is tracked that has moved a sufficient global distance +// from the initial [PointerDownEvent] and it came before a [PointerUpEvent], then +// when this recognizer wins the arena, it will move from the [possible] state to [accepted]. +// * If a [PointerUpEvent] is tracked before the pointer has moved a sufficient global +// distance to be considered a drag, then this recognizer moves from the [possible] +// state to [ready]. +// * If a [PointerCancelEvent] is tracked then this recognizer moves from its current +// state to [ready]. +// +// Once the recognizer has stopped tracking any remaining pointers, the recognizer +// returns to the [ready] state. +enum _DragState { + // The recognizer is ready to start recognizing a drag. + ready, + + // The sequence of pointer events seen thus far is consistent with a drag but + // it has not been accepted definitively. + possible, + + // The sequence of pointer events has been accepted definitively as a drag. + accepted, +} + +/// {@macro flutter.gestures.tap.GestureTapDownCallback} +/// +/// The consecutive tap count at the time the pointer contacted the +/// screen is given by [TapDragDownDetails.consecutiveTapCount]. +/// +/// Used by [TapAndDragGestureRecognizer.onTapDown]. +typedef GestureTapDragDownCallback = void Function(TapDragDownDetails details); + +/// Details for [GestureTapDragDownCallback], such as the number of +/// consecutive taps. +/// +/// See also: +/// +/// * [TapAndDragGestureRecognizer], which passes this information to its +/// [TapAndDragGestureRecognizer.onTapDown] callback. +/// * [TapDragUpDetails], the details for [GestureTapDragUpCallback]. +/// * [TapDragStartDetails], the details for [GestureTapDragStartCallback]. +/// * [TapDragUpdateDetails], the details for [GestureTapDragUpdateCallback]. +/// * [TapDragEndDetails], the details for [GestureTapDragEndCallback]. +class TapDragDownDetails with Diagnosticable { + /// Creates details for a [GestureTapDragDownCallback]. + /// + /// The [globalPosition], [localPosition], [consecutiveTapCount], and + /// [keysPressedOnDown] arguments must be provided and must not be null. + TapDragDownDetails({ + required this.globalPosition, + required this.localPosition, + this.kind, + required this.consecutiveTapCount, + required this.keysPressedOnDown, + }); + + /// The global position at which the pointer contacted the screen. + final Offset globalPosition; + + /// The local position at which the pointer contacted the screen. + final Offset localPosition; + + /// The kind of the device that initiated the event. + final PointerDeviceKind? kind; + + /// If this tap is in a series of taps, then this value represents + /// the number in the series this tap is. + final int consecutiveTapCount; + + /// The keys that were pressed when the most recent [PointerDownEvent] occurred. + final Set keysPressedOnDown; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('globalPosition', globalPosition)); + properties.add(DiagnosticsProperty('localPosition', localPosition)); + properties.add(DiagnosticsProperty('kind', kind)); + properties.add(DiagnosticsProperty('consecutiveTapCount', consecutiveTapCount)); + properties.add(DiagnosticsProperty>('keysPressedOnDown', keysPressedOnDown)); + } +} + +/// {@macro flutter.gestures.tap.GestureTapUpCallback} +/// +/// The consecutive tap count at the time the pointer contacted the +/// screen is given by [TapDragUpDetails.consecutiveTapCount]. +/// +/// Used by [TapAndDragGestureRecognizer.onTapUp]. +typedef GestureTapDragUpCallback = void Function(TapDragUpDetails details); + +/// Details for [GestureTapDragUpCallback], such as the number of +/// consecutive taps. +/// +/// See also: +/// +/// * [TapAndDragGestureRecognizer], which passes this information to its +/// [TapAndDragGestureRecognizer.onTapUp] callback. +/// * [TapDragDownDetails], the details for [GestureTapDragDownCallback]. +/// * [TapDragStartDetails], the details for [GestureTapDragStartCallback]. +/// * [TapDragUpdateDetails], the details for [GestureTapDragUpdateCallback]. +/// * [TapDragEndDetails], the details for [GestureTapDragEndCallback]. +class TapDragUpDetails with Diagnosticable { + /// Creates details for a [GestureTapDragUpCallback]. + /// + /// The [kind], [globalPosition], [localPosition], [consecutiveTapCount], and + /// [keysPressedOnDown] arguments must be provided and must not be null. + TapDragUpDetails({ + required this.kind, + required this.globalPosition, + required this.localPosition, + required this.consecutiveTapCount, + required this.keysPressedOnDown, + }); + + /// The global position at which the pointer contacted the screen. + final Offset globalPosition; + + /// The local position at which the pointer contacted the screen. + final Offset localPosition; + + /// The kind of the device that initiated the event. + final PointerDeviceKind kind; + + /// If this tap is in a series of taps, then this value represents + /// the number in the series this tap is. + final int consecutiveTapCount; + + /// The keys that were pressed when the most recent [PointerDownEvent] occurred. + final Set keysPressedOnDown; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('globalPosition', globalPosition)); + properties.add(DiagnosticsProperty('localPosition', localPosition)); + properties.add(DiagnosticsProperty('kind', kind)); + properties.add(DiagnosticsProperty('consecutiveTapCount', consecutiveTapCount)); + properties.add(DiagnosticsProperty>('keysPressedOnDown', keysPressedOnDown)); + } +} + +/// {@macro flutter.gestures.dragdetails.GestureDragStartCallback} +/// +/// The consecutive tap count at the time the pointer contacted the +/// screen is given by [TapDragStartDetails.consecutiveTapCount]. +/// +/// Used by [TapAndDragGestureRecognizer.onDragStart]. +typedef GestureTapDragStartCallback = void Function(TapDragStartDetails details); + +/// Details for [GestureTapDragStartCallback], such as the number of +/// consecutive taps. +/// +/// See also: +/// +/// * [TapAndDragGestureRecognizer], which passes this information to its +/// [TapAndDragGestureRecognizer.onDragStart] callback. +/// * [TapDragDownDetails], the details for [GestureTapDragDownCallback]. +/// * [TapDragUpDetails], the details for [GestureTapDragUpCallback]. +/// * [TapDragUpdateDetails], the details for [GestureTapDragUpdateCallback]. +/// * [TapDragEndDetails], the details for [GestureTapDragEndCallback]. +class TapDragStartDetails with Diagnosticable { + /// Creates details for a [GestureTapDragStartCallback]. + /// + /// The [globalPosition], [localPosition], [consecutiveTapCount], and + /// [keysPressedOnDown] arguments must be provided and must not be null. + TapDragStartDetails({ + this.sourceTimeStamp, + required this.globalPosition, + required this.localPosition, + this.kind, + required this.consecutiveTapCount, + required this.keysPressedOnDown, + }); + + /// Recorded timestamp of the source pointer event that triggered the drag + /// event. + /// + /// Could be null if triggered from proxied events such as accessibility. + final Duration? sourceTimeStamp; + + /// The global position at which the pointer contacted the screen. + /// + /// See also: + /// + /// * [localPosition], which is the [globalPosition] transformed to the + /// coordinate space of the event receiver. + final Offset globalPosition; + + /// The local position in the coordinate system of the event receiver at + /// which the pointer contacted the screen. + final Offset localPosition; + + /// The kind of the device that initiated the event. + final PointerDeviceKind? kind; + + /// If this tap is in a series of taps, then this value represents + /// the number in the series this tap is. + final int consecutiveTapCount; + + /// The keys that were pressed when the most recent [PointerDownEvent] occurred. + final Set keysPressedOnDown; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('sourceTimeStamp', sourceTimeStamp)); + properties.add(DiagnosticsProperty('globalPosition', globalPosition)); + properties.add(DiagnosticsProperty('localPosition', localPosition)); + properties.add(DiagnosticsProperty('kind', kind)); + properties.add(DiagnosticsProperty('consecutiveTapCount', consecutiveTapCount)); + properties.add(DiagnosticsProperty>('keysPressedOnDown', keysPressedOnDown)); + } +} + +/// {@macro flutter.gestures.dragdetails.GestureDragUpdateCallback} +/// +/// The consecutive tap count at the time the pointer contacted the +/// screen is given by [TapDragUpdateDetails.consecutiveTapCount]. +/// +/// Used by [TapAndDragGestureRecognizer.onDragUpdate]. +typedef GestureTapDragUpdateCallback = void Function(TapDragUpdateDetails details); + +/// Details for [GestureTapDragUpdateCallback], such as the number of +/// consecutive taps. +/// +/// See also: +/// +/// * [TapAndDragGestureRecognizer], which passes this information to its +/// [TapAndDragGestureRecognizer.onDragUpdate] callback. +/// * [TapDragDownDetails], the details for [GestureTapDragDownCallback]. +/// * [TapDragUpDetails], the details for [GestureTapDragUpCallback]. +/// * [TapDragStartDetails], the details for [GestureTapDragStartCallback]. +/// * [TapDragEndDetails], the details for [GestureTapDragEndCallback]. +class TapDragUpdateDetails with Diagnosticable { + /// Creates details for a [GestureTapDragUpdateCallback]. + /// + /// The [delta] argument must not be null. + /// + /// If [primaryDelta] is non-null, then its value must match one of the + /// coordinates of [delta] and the other coordinate must be zero. + /// + /// The [globalPosition], [localPosition], [offsetFromOrigin], [localOffsetFromOrigin], + /// [consecutiveTapCount], and [keysPressedOnDown] arguments must be provided and must + /// not be null. + TapDragUpdateDetails({ + this.sourceTimeStamp, + this.delta = Offset.zero, + this.primaryDelta, + required this.globalPosition, + this.kind, + required this.localPosition, + required this.offsetFromOrigin, + required this.localOffsetFromOrigin, + required this.consecutiveTapCount, + required this.keysPressedOnDown, + }) : assert(delta != null), + assert( + primaryDelta == null + || (primaryDelta == delta.dx && delta.dy == 0.0) + || (primaryDelta == delta.dy && delta.dx == 0.0), + ); + + /// Recorded timestamp of the source pointer event that triggered the drag + /// event. + /// + /// Could be null if triggered from proxied events such as accessibility. + final Duration? sourceTimeStamp; + + /// The amount the pointer has moved in the coordinate space of the event + /// receiver since the previous update. + /// + /// If the [GestureTapDragUpdateCallback] is for a one-dimensional drag (e.g., + /// a horizontal or vertical drag), then this offset contains only the delta + /// in that direction (i.e., the coordinate in the other direction is zero). + /// + /// Defaults to zero if not specified in the constructor. + final Offset delta; + + /// The amount the pointer has moved along the primary axis in the coordinate + /// space of the event receiver since the previous + /// update. + /// + /// If the [GestureTapDragUpdateCallback] is for a one-dimensional drag (e.g., + /// a horizontal or vertical drag), then this value contains the component of + /// [delta] along the primary axis (e.g., horizontal or vertical, + /// respectively). Otherwise, if the [GestureTapDragUpdateCallback] is for a + /// two-dimensional drag (e.g., a pan), then this value is null. + /// + /// Defaults to null if not specified in the constructor. + final double? primaryDelta; + + /// The pointer's global position when it triggered this update. + /// + /// See also: + /// + /// * [localPosition], which is the [globalPosition] transformed to the + /// coordinate space of the event receiver. + final Offset globalPosition; + + /// The local position in the coordinate system of the event receiver at + /// which the pointer contacted the screen. + /// + /// Defaults to [globalPosition] if not specified in the constructor. + final Offset localPosition; + + /// The kind of the device that initiated the event. + final PointerDeviceKind? kind; + + /// A delta offset from the point where the drag initially contacted + /// the screen to the point where the pointer is currently located in global + /// coordinates (the present [globalPosition]) when this callback is triggered. + /// + /// When considering a [GestureRecognizer] that tracks the number of consecutive taps, + /// this offset is associated with the most recent [PointerDownEvent] that occured. + final Offset offsetFromOrigin; + + /// A local delta offset from the point where the drag initially contacted + /// the screen to the point where the pointer is currently located in local + /// coordinates (the present [localPosition]) when this callback is triggered. + /// + /// When considering a [GestureRecognizer] that tracks the number of consecutive taps, + /// this offset is associated with the most recent [PointerDownEvent] that occured. + final Offset localOffsetFromOrigin; + + /// If this tap is in a series of taps, then this value represents + /// the number in the series this tap is. + final int consecutiveTapCount; + + /// The keys that were pressed when the most recent [PointerDownEvent] occurred. + final Set keysPressedOnDown; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('sourceTimeStamp', sourceTimeStamp)); + properties.add(DiagnosticsProperty('delta', delta)); + properties.add(DiagnosticsProperty('primaryDelta', primaryDelta)); + properties.add(DiagnosticsProperty('globalPosition', globalPosition)); + properties.add(DiagnosticsProperty('localPosition', localPosition)); + properties.add(DiagnosticsProperty('kind', kind)); + properties.add(DiagnosticsProperty('offsetFromOrigin', offsetFromOrigin)); + properties.add(DiagnosticsProperty('localOffsetFromOrigin', localOffsetFromOrigin)); + properties.add(DiagnosticsProperty('consecutiveTapCount', consecutiveTapCount)); + properties.add(DiagnosticsProperty>('keysPressedOnDown', keysPressedOnDown)); + } +} + +/// {@macro flutter.gestures.monodrag.GestureDragEndCallback} +/// +/// The consecutive tap count at the time the pointer contacted the +/// screen is given by [TapDragEndDetails.consecutiveTapCount]. +/// +/// Used by [TapAndDragGestureRecognizer.onDragEnd]. +typedef GestureTapDragEndCallback = void Function(TapDragEndDetails endDetails); + +/// Details for [GestureTapDragEndCallback], such as the number of +/// consecutive taps. +/// +/// See also: +/// +/// * [TapAndDragGestureRecognizer], which passes this information to its +/// [TapAndDragGestureRecognizer.onDragEnd] callback. +/// * [TapDragDownDetails], the details for [GestureTapDragDownCallback]. +/// * [TapDragUpDetails], the details for [GestureTapDragUpCallback]. +/// * [TapDragStartDetails], the details for [GestureTapDragStartCallback]. +/// * [TapDragUpdateDetails], the details for [GestureTapDragUpdateCallback]. +class TapDragEndDetails with Diagnosticable { + /// Creates details for a [GestureTapDragEndCallback]. + /// + /// The [velocity] argument must not be null. + /// + /// The [consecutiveTapCount], and [keysPressedOnDown] arguments must + /// be provided and must not be null. + TapDragEndDetails({ + this.velocity = Velocity.zero, + this.primaryVelocity, + required this.consecutiveTapCount, + required this.keysPressedOnDown, + }) : assert(velocity != null), + assert( + primaryVelocity == null + || primaryVelocity == velocity.pixelsPerSecond.dx + || primaryVelocity == velocity.pixelsPerSecond.dy, + ); + + /// The velocity the pointer was moving when it stopped contacting the screen. + /// + /// Defaults to zero if not specified in the constructor. + final Velocity velocity; + + /// The velocity the pointer was moving along the primary axis when it stopped + /// contacting the screen, in logical pixels per second. + /// + /// If the [GestureTapDragEndCallback] is for a one-dimensional drag (e.g., a + /// horizontal or vertical drag), then this value contains the component of + /// [velocity] along the primary axis (e.g., horizontal or vertical, + /// respectively). Otherwise, if the [GestureTapDragEndCallback] is for a + /// two-dimensional drag (e.g., a pan), then this value is null. + /// + /// Defaults to null if not specified in the constructor. + final double? primaryVelocity; + + /// If this tap is in a series of taps, then this value represents + /// the number in the series this tap is. + final int consecutiveTapCount; + + /// The keys that were pressed when the most recent [PointerDownEvent] occurred. + final Set keysPressedOnDown; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('velocity', velocity)); + properties.add(DiagnosticsProperty('primaryVelocity', primaryVelocity)); + properties.add(DiagnosticsProperty('consecutiveTapCount', consecutiveTapCount)); + properties.add(DiagnosticsProperty>('keysPressedOnDown', keysPressedOnDown)); + } +} + +/// Signature for when the pointer that previously triggered a +/// [GestureTapDragDownCallback] did not complete. +/// +/// Used by [TapAndDragGestureRecognizer.onCancel]. +typedef GestureCancelCallback = void Function(); + +// A mixin for [OneSequenceGestureRecognizer] that tracks the number of taps +// that occur in a series of [PointerEvent]s and the most recent set of +// [LogicalKeyboardKey]s pressed on the most recent tap down. +// +// A tap is tracked as part of a series of taps if: +// +// 1. The elapsed time between when a [PointerUpEvent] and the subsequent +// [PointerDownEvent] does not exceed [kDoubleTapTimeout]. +// 2. The delta between the position tapped in the global coordinate system +// and the position that was tapped previously must be less than or equal +// to [kDoubleTapSlop]. +// +// This mixin's state, i.e. the series of taps being tracked is reset when +// a tap is tracked that does not meet any of the specifications stated above. +mixin _TapStatusTrackerMixin on OneSequenceGestureRecognizer { + // Public state available to [OneSequenceGestureRecognizer]. + + // The [PointerDownEvent] that was most recently tracked in [addAllowedPointer]. + // + // This value will be null if a [PointerDownEvent] has not been tracked yet in + // [addAllowedPointer] or the timer between two taps has elapsed. + // + // This value is only reset when the timer between a [PointerUpEvent] and the + // [PointerDownEvent] times out or when a new [PointerDownEvent] is tracked in + // [addAllowedPointer]. + PointerDownEvent? get currentDown => _down; + + // The [PointerUpEvent] that was most recently tracked in [handleEvent]. + // + // This value will be null if a [PointerUpEvent] has not been tracked yet in + // [handleEvent] or the timer between two taps has elapsed. + // + // This value is only reset when the timer between a [PointerUpEvent] and the + // [PointerDownEvent] times out or when a new [PointerDownEvent] is tracked in + // [addAllowedPointer]. + PointerUpEvent? get currentUp => _up; + + // The number of consecutive taps that the most recently tracked [PointerDownEvent] + // in [currentDown] represents. + // + // This value defaults to zero, meaning a tap series is not currently being tracked. + // + // When this value is greater than zero it means [addAllowedPointer] has run + // and at least one [PointerDownEvent] belongs to the current series of taps + // being tracked. + // + // [addAllowedPointer] will either increment this value by `1` or set the value to `1` + // depending if the new [PointerDownEvent] is determined to be in the same series as the + // tap that preceded it. If too much time has elapsed between two taps, the recognizer has lost + // in the arena, the gesture has been cancelled, or the recognizer is being disposed then + // this value will be set to `0`, and a new series will begin. + int get consecutiveTapCount => _consecutiveTapCount; + + // The set of [LogicalKeyboardKey]s pressed when the most recent [PointerDownEvent] + // was tracked in [addAllowedPointer]. + // + // This value defaults to an empty set. + // + // When the timer between two taps elapses, the recognizer loses the arena, the gesture is cancelled + // or the recognizer is disposed of then this value is reset. + Set get keysPressedOnDown => _keysPressedOnDown ?? {}; + + // The upper limit for the [consecutiveTapCount]. When this limit is reached + // all tap related state is reset and a new tap series is tracked. + // + // If this value is null, [consecutiveTapCount] can grow infinitely large. + int? get maxConsecutiveTap; + + // The maximum distance in logical pixels the gesture is allowed to drift + // from the initial touch down position before the [consecutiveTapCount] + // and [keysPressedOnDown] are frozen and the remaining tracker state is + // reset. These values remain frozen until the next [PointerDownEvent] is + // tracked in [addAllowedPointer]. + double? get slopTolerance; + + // Private tap state tracked. + PointerDownEvent? _down; + PointerUpEvent? _up; + int _consecutiveTapCount = 0; + Set? _keysPressedOnDown; + + OffsetPair? _originPosition; + int? _previousButtons; + + // For timing taps. + Timer? _consecutiveTapTimer; + Offset? _lastTapOffset; + + // When tracking a tap, the [consecutiveTapCount] is incremented if the given tap + // falls under the tolerance specifications and reset to 1 if not. + @override + void addAllowedPointer(PointerDownEvent event) { + super.addAllowedPointer(event); + if (maxConsecutiveTap == _consecutiveTapCount) { + _tapTrackerReset(); + } + _up = null; + if (_down != null && !_representsSameSeries(event)) { + // The given tap does not match the specifications of the series of taps being tracked, + // reset the tap count and related state. + _consecutiveTapCount = 1; + } else { + _consecutiveTapCount += 1; + } + _consecutiveTapTimerStop(); + // `_down` must be assigned in this method instead of [handleEvent], + // because [acceptGesture] might be called before [handleEvent], + // which may rely on `_down` to initiate a callback. + _trackTap(event); + } + + @override + void handleEvent(PointerEvent event) { + if (event is PointerMoveEvent) { + final bool isSlopPastTolerance = slopTolerance != null && _getGlobalDistance(event, _originPosition) > slopTolerance!; + + if (isSlopPastTolerance) { + _consecutiveTapTimerStop(); + _previousButtons = null; + _lastTapOffset = null; + } + } else if (event is PointerUpEvent) { + _up = event; + if (_down != null) { + _consecutiveTapTimerStop(); + _consecutiveTapTimerStart(); + } + } else if (event is PointerCancelEvent) { + _tapTrackerReset(); + } + } + + @override + void rejectGesture(int pointer) { + _tapTrackerReset(); + } + + @override + void dispose() { + _tapTrackerReset(); + super.dispose(); + } + + void _trackTap(PointerDownEvent event) { + _down = event; + _keysPressedOnDown = HardwareKeyboard.instance.logicalKeysPressed; + _previousButtons = event.buttons; + _lastTapOffset = event.position; + _originPosition = OffsetPair(local: event.localPosition, global: event.position); + } + + bool _hasSameButton(int buttons) { + assert(_previousButtons != null); + if (buttons == _previousButtons!) { + return true; + } else { + return false; + } + } + + bool _isWithinConsecutiveTapTolerance(Offset secondTapOffset) { + assert(secondTapOffset != null); + if (_lastTapOffset == null) { + return false; + } + + final Offset difference = secondTapOffset - _lastTapOffset!; + return difference.distance <= kDoubleTapSlop; + } + + bool _representsSameSeries(PointerDownEvent event) { + return _consecutiveTapTimer != null + && _isWithinConsecutiveTapTolerance(event.position) + && _hasSameButton(event.buttons); + } + + void _consecutiveTapTimerStart() { + _consecutiveTapTimer ??= Timer(kDoubleTapTimeout, _tapTrackerReset); + } + + void _consecutiveTapTimerStop() { + if (_consecutiveTapTimer != null) { + _consecutiveTapTimer!.cancel(); + _consecutiveTapTimer = null; + } + } + + void _tapTrackerReset() { + // The timer has timed out, i.e. the time between a [PointerUpEvent] and the subsequent + // [PointerDownEvent] exceeded the duration of [kDoubleTapTimeout], so the tap belonging + // to the [PointerDownEvent] cannot be considered part of the same tap series as the + // previous [PointerUpEvent]. + _consecutiveTapTimerStop(); + _previousButtons = null; + _originPosition = null; + _lastTapOffset = null; + _consecutiveTapCount = 0; + _keysPressedOnDown = null; + _down = null; + _up = null; + } +} + +/// Recognizes taps and movements. +/// +/// Takes on the responsibilities of [TapGestureRecognizer] and +/// [DragGestureRecognizer] in one [GestureRecognizer]. +/// +/// ### Gesture arena behavior +/// +/// [TapAndDragGestureRecognizer] competes on the pointer events of +/// [kPrimaryButton] only when it has at least one non-null `onTap*` +/// or `onDrag*` callback. +/// +/// It will declare defeat if it determines that a gesture is not a +/// tap (e.g. if the pointer is dragged too far while it's contacting the +/// screen) or a drag (e.g. if the pointer was not dragged far enough to +/// be considered a drag. +/// +/// This recognizer will not immediately declare victory for every tap or drag that it +/// recognizes. +/// +/// The recognizer will declare victory when all other recognizer's in +/// the arena have lost, if the timer of [kPressTimeout] elapses and a tap +/// series greater than 1 is being tracked. +/// +/// If this recognizer loses the arena (either by declaring defeat or by +/// another recognizer declaring victory) while the pointer is contacting the +/// screen, it will fire [onCancel] instead of [onTapUp] or [onDragEnd]. +/// +/// ### When competing with `TapGestureRecognizer` and `DragGestureRecognizer` +/// +/// Similar to [TapGestureRecognizer] and [DragGestureRecognizer], +/// [TapAndDragGestureRecognizer] will not aggresively declare victory when it detects +/// a tap, so when it is competing with those gesture recognizers and others it has a chance +/// of losing. +/// +/// When competing against [TapGestureRecognizer], if the pointer does not move past the tap +/// tolerance, then the recognizer that entered the arena first will win. In this case the +/// gesture detected is a tap. If the pointer does travel past the tap tolerance then this +/// recognizer will be declared winner by default. The gesture detected in this case is a drag. +/// +/// When competing against [DragGestureRecognizer], if the pointer does not move a sufficient +/// global distance to be considered a drag, the recognizers will tie in the arena. If the +/// pointer does travel enough distance then the [TapAndDragGestureRecognizer] will lose because +/// the [DragGestureRecognizer] will declare self-victory when the drag threshold is met. +class TapAndDragGestureRecognizer extends OneSequenceGestureRecognizer with _TapStatusTrackerMixin { + /// Creates a tap and drag gesture recognizer. + /// + /// {@macro flutter.gestures.GestureRecognizer.supportedDevices} + TapAndDragGestureRecognizer({ + super.debugOwner, + super.kind, + super.supportedDevices, + }) : _deadline = kPressTimeout, + dragStartBehavior = DragStartBehavior.start, + slopTolerance = kTouchSlop; + + /// Configure the behavior of offsets passed to [onDragStart]. + /// + /// If set to [DragStartBehavior.start], the [onDragStart] callback will be called + /// with the position of the pointer at the time this gesture recognizer won + /// the arena. If [DragStartBehavior.down], [onDragStart] will be called with + /// the position of the first detected down event for the pointer. When there + /// are no other gestures competing with this gesture in the arena, there's + /// no difference in behavior between the two settings. + /// + /// For more information about the gesture arena: + /// https://flutter.dev/docs/development/ui/advanced/gestures#gesture-disambiguation + /// + /// By default, the drag start behavior is [DragStartBehavior.start]. + /// + /// See also: + /// + /// * [DragGestureRecognizer.dragStartBehavior], which includes more details and an example. + DragStartBehavior dragStartBehavior; + + /// The frequency at which the [onDragUpdate] callback is called. + /// + /// The value defaults to null, meaning there is no delay for [onDragUpdate] callback. + /// + /// See also: + /// * [TextSelectionGestureDetector], which uses this parameter to avoid excessive updates + /// text layouts in text fields. + Duration? dragUpdateThrottleFrequency; + + /// An upper bound for the amount of taps that can belong to one tap series. + /// + /// When this limit is reached the series of taps being tracked by this + /// recognizer will be reset. + @override + int? maxConsecutiveTap; + + // The maximum distance in logical pixels the gesture is allowed to drift + // to still be considered a tap. + // + // Drifting past the allowed slop amount causes the recognizer to reset + // the tap series it is currently tracking, stopping the consecutive tap + // count from increasing. The consecutive tap count and the set of hardware + // keys that were pressed on tap down will retain their pre-past slop + // tolerance values until the next [PointerDownEvent] is tracked. + // + // If the gesture exceeds this value, then it can only be accepted as a drag + // gesture. + // + // Can be null to indicate that the gesture can drift for any distance. + // Defaults to 18 logical pixels. + @override + final double? slopTolerance; + + /// {@macro flutter.gestures.tap.TapGestureRecognizer.onTapDown} + /// + /// This triggers after the down event, once a short timeout ([kPressTimeout]) has + /// elapsed, or once the gestures has won the arena, whichever comes first. + /// + /// The position of the pointer is provided in the callback's `details` + /// argument, which is a [TapDragDownDetails] object. + /// + /// {@template flutter.gestures.selectionrecognizers.TapAndDragGestureRecognizer.tapStatusTrackerData} + /// The number of consecutive taps, and the keys that were pressed on tap down + /// are also provided in the callback's `details` argument. + /// {@endtemplate} + /// + /// See also: + /// + /// * [kPrimaryButton], the button this callback responds to. + /// * [TapDragDownDetails], which is passed as an argument to this callback. + GestureTapDragDownCallback? onTapDown; + + /// {@macro flutter.gestures.tap.TapGestureRecognizer.onTapUp} + /// + /// This triggers on the up event, if the recognizer wins the arena with it + /// or has previously won. + /// + /// The position of the pointer is provided in the callback's `details` + /// argument, which is a [TapDragUpDetails] object. + /// + /// {@macro flutter.gestures.selectionrecognizers.TapAndDragGestureRecognizer.tapStatusTrackerData} + /// + /// See also: + /// + /// * [kPrimaryButton], the button this callback responds to. + /// * [TapDragUpDetails], which is passed as an argument to this callback. + GestureTapDragUpCallback? onTapUp; + + /// {@macro flutter.gestures.monodrag.DragGestureRecognizer.onStart} + /// + /// The position of the pointer is provided in the callback's `details` + /// argument, which is a [TapDragStartDetails] object. The [dragStartBehavior] + /// determines this position. + /// + /// {@macro flutter.gestures.selectionrecognizers.TapAndDragGestureRecognizer.tapStatusTrackerData} + /// + /// See also: + /// + /// * [kPrimaryButton], the button this callback responds to. + /// * [TapDragStartDetails], which is passed as an argument to this callback. + GestureTapDragStartCallback? onDragStart; + + /// {@macro flutter.gestures.monodrag.DragGestureRecognizer.onUpdate} + /// + /// The distance traveled by the pointer since the last update is provided in + /// the callback's `details` argument, which is a [TapDragUpdateDetails] object. + /// + /// {@macro flutter.gestures.selectionrecognizers.TapAndDragGestureRecognizer.tapStatusTrackerData} + /// + /// See also: + /// + /// * [kPrimaryButton], the button this callback responds to. + /// * [TapDragUpdateDetails], which is passed as an argument to this callback. + GestureTapDragUpdateCallback? onDragUpdate; + + /// {@macro flutter.gestures.monodrag.DragGestureRecognizer.onEnd} + /// + /// The velocity is provided in the callback's `details` argument, which is a + /// [TapDragEndDetails] object. + /// + /// {@macro flutter.gestures.selectionrecognizers.TapAndDragGestureRecognizer.tapStatusTrackerData} + /// + /// See also: + /// + /// * [kPrimaryButton], the button this callback responds to. + /// * [TapDragEndDetails], which is passed as an argument to this callback. + GestureTapDragEndCallback? onDragEnd; + + /// The pointer that previously triggered [onTapDown] did not complete. + /// + /// This is called when a [PointerCancelEvent] is tracked when the [onTapDown] callback + /// was previously called. + /// + /// It may also be called if a [PointerUpEvent] is tracked after the pointer has moved + /// past the tap tolerance but not past the drag tolerance, and the recognizer has not + /// yet won the arena. + /// + /// See also: + /// + /// * [kPrimaryButton], the button this callback responds to. + GestureCancelCallback? onCancel; + + // Tap related state. + bool _pastSlopTolerance = false; + bool _sentTapDown = false; + + // Primary pointer being tracked by this recognizer. + int? _primaryPointer; + Timer? _deadlineTimer; + // The recognizer will call [onTapDown] after this amount of time has elapsed + // since starting to track the primary pointer. + // + // [onTapDown] will not be called if the primary pointer is + // accepted, rejected, or all pointers are up or canceled before [_deadline]. + final Duration _deadline; + + // Drag related state. + _DragState _dragState = _DragState.ready; + PointerEvent? _start; + late OffsetPair _initialPosition; + late double _globalDistanceMoved; + OffsetPair? _correctedPosition; + + // For drag update throttle. + TapDragUpdateDetails? _lastDragUpdateDetails; + Timer? _dragUpdateThrottleTimer; + + final Set _acceptedActivePointers = {}; + + bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind, double? deviceTouchSlop) { + return _globalDistanceMoved.abs() > computePanSlop(pointerDeviceKind, gestureSettings); + } + + // Drag updates may require throttling to avoid excessive updating, such as for text layouts in text + // fields. The frequency of invocations is controlled by the [dragUpdateThrottleFrequency]. + // + // Once the drag gesture ends, any pending drag update will be fired + // immediately. See [_checkDragEnd]. + void _handleDragUpdateThrottled() { + assert(_lastDragUpdateDetails != null); + if (onDragUpdate != null) { + invokeCallback('onDragUpdate', () => onDragUpdate!(_lastDragUpdateDetails!)); + } + _dragUpdateThrottleTimer = null; + _lastDragUpdateDetails = null; + } + + @override + bool isPointerAllowed(PointerEvent event) { + if (_primaryPointer == null) { + switch (event.buttons) { + case kPrimaryButton: + if (onTapDown == null && + onDragStart == null && + onDragUpdate == null && + onDragEnd == null && + onTapUp == null && + onCancel == null) { + return false; + } + break; + default: + return false; + } + } else { + if (event.pointer != _primaryPointer) { + return false; + } + } + + return super.isPointerAllowed(event as PointerDownEvent); + } + + @override + void addAllowedPointer(PointerDownEvent event) { + if (_dragState == _DragState.ready) { + super.addAllowedPointer(event); + _primaryPointer = event.pointer; + _globalDistanceMoved = 0.0; + _dragState = _DragState.possible; + _initialPosition = OffsetPair(global: event.position, local: event.localPosition); + _deadlineTimer = Timer(_deadline, () => _didExceedDeadlineWithEvent(event)); + } + } + + @override + void handleNonAllowedPointer(PointerDownEvent event) { + // There can be multiple drags simultaneously. Their effects are combined. + if (event.buttons != kPrimaryButton) { + if (!_sentTapDown) { + super.handleNonAllowedPointer(event); + } + } + } + + @override + void acceptGesture(int pointer) { + if (pointer != _primaryPointer) { + return; + } + + _stopDeadlineTimer(); + + assert(!_acceptedActivePointers.contains(pointer)); + _acceptedActivePointers.add(pointer); + + // Called when this recognizer is accepted by the [GestureArena]. + if (currentDown != null) { + _checkTapDown(currentDown!); + } + + if (_start != null) { + _acceptDrag(_start!); + } + + if (currentUp != null) { + _checkTapUp(currentUp!); + } + } + + @override + void didStopTrackingLastPointer(int pointer) { + switch (_dragState) { + case _DragState.ready: + _checkCancel(); + resolve(GestureDisposition.rejected); + break; + + case _DragState.possible: + if (_pastSlopTolerance) { + // This means the pointer was not accepted as a tap. + if (_sentTapDown) { + // If the recognizer has already won the arena for the primary pointer being tracked + // but the pointer has exceeded the tap tolerance, then the pointer is accepted as a + // drag gesture. + if (currentDown != null) { + _acceptDrag(currentDown!); + _checkDragEnd(); + } + } else { + _checkCancel(); + resolve(GestureDisposition.rejected); + } + } else { + // The pointer is accepted as a tap. + if (currentUp != null) { + _checkTapUp(currentUp!); + } + } + break; + + case _DragState.accepted: + // For the case when the pointer has been accepted as a drag. + // Meaning [_checkTapDown] and [_checkDragStart] have already ran. + _checkDragEnd(); + break; + } + + _stopDeadlineTimer(); + _dragState = _DragState.ready; + _pastSlopTolerance = false; + } + + @override + void handleEvent(PointerEvent event) { + if (event.pointer != _primaryPointer) { + return; + } + super.handleEvent(event); + if (event is PointerMoveEvent) { + // Receiving a [PointerMoveEvent], does not automatically mean the pointer + // being tracked is doing a drag gesture. There is some drift that can happen + // between the initial [PointerDownEvent] and subsequent [PointerMoveEvent]s. + // Accessing [_pastSlopTolerance] lets us know if our tap has moved past the + // acceptable tolerance. If the pointer does not move past this tolerance than + // it is not considered a drag. + // + // To be recognized as a drag, the [PointerMoveEvent] must also have moved + // a sufficient global distance from the initial [PointerDownEvent] to be + // accepted as a drag. This logic is handled in [_hasSufficientGlobalDistanceToAccept]. + // + // The recognizer will also detect the gesture as a drag when the pointer + // has been accepted and it has moved past the [slopTolerance] but has not moved + // a sufficient global distance from the initial position to be considered a drag. + // In this case since the gesture cannot be a tap, it defaults to a drag. + + _pastSlopTolerance = _pastSlopTolerance || slopTolerance != null && _getGlobalDistance(event, _initialPosition) > slopTolerance!; + + if (_dragState == _DragState.accepted) { + _checkDragUpdate(event); + } else if (_dragState == _DragState.possible) { + if (_start == null) { + // Only check for a drag if the start of a drag was not already identified. + _checkDrag(event); + } + + // This can occur when the recognizer is accepted before a [PointerMoveEvent] has been + // received that moves the pointer a sufficient global distance to be considered a drag. + if (_start != null && _sentTapDown) { + _acceptDrag(_start!); + } + } + } else if (event is PointerUpEvent) { + if (_dragState == _DragState.possible) { + // The drag has not been accepted before a [PointerUpEvent], therefore the recognizer + // attempts to recognize a tap. + stopTrackingIfPointerNoLongerDown(event); + } else if (_dragState == _DragState.accepted) { + _giveUpPointer(event.pointer); + } + } else if (event is PointerCancelEvent) { + _dragState = _DragState.ready; + _giveUpPointer(event.pointer); + } + } + + @override + void rejectGesture(int pointer) { + if (pointer != _primaryPointer) { + return; + } + super.rejectGesture(pointer); + + _stopDeadlineTimer(); + _giveUpPointer(pointer); + _resetTaps(); + _resetDragUpdateThrottle(); + } + + @override + void dispose() { + _stopDeadlineTimer(); + _resetDragUpdateThrottle(); + super.dispose(); + } + + @override + String get debugDescription => 'tap_and_drag'; + + void _acceptDrag(PointerEvent event) { + _dragState = _DragState.accepted; + if (dragStartBehavior == DragStartBehavior.start) { + _initialPosition = _initialPosition + OffsetPair(global: event.delta, local: event.localDelta); + } + _checkDragStart(event); + if (event.localDelta != Offset.zero) { + final Matrix4? localToGlobal = event.transform != null ? Matrix4.tryInvert(event.transform!) : null; + final Offset correctedLocalPosition = _initialPosition.local + event.localDelta; + final Offset globalUpdateDelta = PointerEvent.transformDeltaViaPositions( + untransformedEndPosition: correctedLocalPosition, + untransformedDelta: event.localDelta, + transform: localToGlobal, + ); + final OffsetPair updateDelta = OffsetPair(local: event.localDelta, global: globalUpdateDelta); + _correctedPosition = _initialPosition + updateDelta; // Only adds delta for down behaviour + _checkDragUpdate(event); + _correctedPosition = null; + } + } + + void _checkDrag(PointerMoveEvent event) { + final Matrix4? localToGlobalTransform = event.transform == null ? null : Matrix4.tryInvert(event.transform!); + _globalDistanceMoved += PointerEvent.transformDeltaViaPositions( + transform: localToGlobalTransform, + untransformedDelta: event.localDelta, + untransformedEndPosition: event.localPosition + ).distance * 1.sign; + if (_hasSufficientGlobalDistanceToAccept(event.kind, gestureSettings?.touchSlop)) { + _start = event; + } + } + + void _checkTapDown(PointerDownEvent event) { + if (_sentTapDown) { + return; + } + + final TapDragDownDetails details = TapDragDownDetails( + globalPosition: event.position, + localPosition: event.localPosition, + kind: getKindForPointer(event.pointer), + consecutiveTapCount: consecutiveTapCount, + keysPressedOnDown: keysPressedOnDown, + ); + + if (onTapDown != null) { + invokeCallback('onTapDown', () => onTapDown!(details)); + } + + _sentTapDown = true; + } + + void _checkTapUp(PointerUpEvent event) { + if (!_sentTapDown) { + return; + } + + final TapDragUpDetails upDetails = TapDragUpDetails( + kind: event.kind, + globalPosition: event.position, + localPosition: event.localPosition, + consecutiveTapCount: consecutiveTapCount, + keysPressedOnDown: keysPressedOnDown, + ); + + if (onTapUp != null) { + invokeCallback('onTapUp', () => onTapUp!(upDetails)); + } + + _resetTaps(); + if (!_acceptedActivePointers.remove(event.pointer)) { + resolvePointer(event.pointer, GestureDisposition.rejected); + } + } + + void _checkDragStart(PointerEvent event) { + if (onDragStart != null) { + final TapDragStartDetails details = TapDragStartDetails( + sourceTimeStamp: event.timeStamp, + globalPosition: _initialPosition.global, + localPosition: _initialPosition.local, + kind: getKindForPointer(event.pointer), + consecutiveTapCount: consecutiveTapCount, + keysPressedOnDown: keysPressedOnDown, + ); + + invokeCallback('onDragStart', () => onDragStart!(details)); + } + + _start = null; + } + + void _checkDragUpdate(PointerEvent event) { + final Offset globalPosition = _correctedPosition != null ? _correctedPosition!.global : event.position; + final Offset localPosition = _correctedPosition != null ? _correctedPosition!.local : event.localPosition; + + final TapDragUpdateDetails details = TapDragUpdateDetails( + sourceTimeStamp: event.timeStamp, + delta: event.localDelta, + globalPosition: globalPosition, + kind: getKindForPointer(event.pointer), + localPosition: localPosition, + offsetFromOrigin: globalPosition - _initialPosition.global, + localOffsetFromOrigin: localPosition - _initialPosition.local, + consecutiveTapCount: consecutiveTapCount, + keysPressedOnDown: keysPressedOnDown, + ); + + if (dragUpdateThrottleFrequency != null) { + _lastDragUpdateDetails = details; + // Only schedule a new timer if there's not one pending. + _dragUpdateThrottleTimer ??= Timer(dragUpdateThrottleFrequency!, _handleDragUpdateThrottled); + } else { + if (onDragUpdate != null) { + invokeCallback('onDragUpdate', () => onDragUpdate!(details)); + } + } + } + + void _checkDragEnd() { + if (_dragUpdateThrottleTimer != null) { + // If there's already an update scheduled, trigger it immediately and + // cancel the timer. + _dragUpdateThrottleTimer!.cancel(); + _handleDragUpdateThrottled(); + } + + final TapDragEndDetails endDetails = + TapDragEndDetails( + primaryVelocity: 0.0, + consecutiveTapCount: consecutiveTapCount, + keysPressedOnDown: keysPressedOnDown, + ); + + invokeCallback('onDragEnd', () => onDragEnd!(endDetails)); + + _resetTaps(); + _resetDragUpdateThrottle(); + } + + void _checkCancel() { + if (!_sentTapDown) { + // Do not fire tap cancel if [onTapDown] was never called. + return; + } + if (onCancel != null) { + invokeCallback('onCancel', onCancel!); + } + _resetDragUpdateThrottle(); + _resetTaps(); + } + + void _didExceedDeadlineWithEvent(PointerDownEvent event) { + _didExceedDeadline(); + } + + void _didExceedDeadline() { + if (currentDown != null) { + _checkTapDown(currentDown!); + + if (consecutiveTapCount > 1) { + // If our consecutive tap count is greater than 1, i.e. is a double tap or greater, + // then this recognizer declares victory to prevent the [LongPressGestureRecognizer] + // from declaring itself the winner if a double tap is held for too long. + resolve(GestureDisposition.accepted); + } + } + } + + void _giveUpPointer(int pointer) { + stopTrackingPointer(pointer); + // If the pointer was never accepted, then it is rejected since this recognizer is no longer + // interested in winning the gesture arena for it. + if (!_acceptedActivePointers.remove(pointer)) { + resolvePointer(pointer, GestureDisposition.rejected); + } + } + + void _resetTaps() { + _sentTapDown = false; + _primaryPointer = null; + } + + void _resetDragUpdateThrottle() { + if (dragUpdateThrottleFrequency == null) { + return; + } + _lastDragUpdateDetails = null; + if (_dragUpdateThrottleTimer != null) { + _dragUpdateThrottleTimer!.cancel(); + _dragUpdateThrottleTimer = null; + } + } + + void _stopDeadlineTimer() { + if (_deadlineTimer != null) { + _deadlineTimer!.cancel(); + _deadlineTimer = null; + } + } +} diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 8812ec5b5fb0a..21fd50ef6e554 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -24,6 +24,7 @@ import 'gesture_detector.dart'; import 'magnifier.dart'; import 'overlay.dart'; import 'scrollable.dart'; +import 'tap_and_drag_gestures.dart'; import 'tap_region.dart'; import 'ticker_provider.dart'; import 'transitions.dart'; @@ -35,19 +36,6 @@ export 'package:flutter/services.dart' show TextSelectionDelegate; /// called. const Duration _kDragSelectionUpdateThrottle = Duration(milliseconds: 50); -/// Signature for when a pointer that's dragging to select text has moved again. -/// -/// The first argument [startDetails] contains the details of the event that -/// initiated the dragging. -/// -/// The second argument [updateDetails] contains the details of the current -/// pointer movement. It's the same as the one passed to [DragGestureRecognizer.onUpdate]. -/// -/// This signature is different from [GestureDragUpdateCallback] to make it -/// easier for various text fields to use [TextSelectionGestureDetector] without -/// having to store the start position. -typedef DragSelectionUpdateCallback = void Function(DragStartDetails startDetails, DragUpdateDetails updateDetails); - /// The type for a Function that builds a toolbar's container with the given /// child. /// @@ -1907,6 +1895,23 @@ class TextSelectionGestureDetectorBuilder { && selection.end >= textPosition.offset; } + /// Returns true if position was on selection. + bool _positionOnSelection(Offset position, TextSelection? targetSelection) { + if (targetSelection == null) { + return false; + } + + final TextPosition textPosition = renderEditable.getPositionForPoint(position); + + return targetSelection.start <= textPosition.offset + && targetSelection.end >= textPosition.offset; + } + + /// Returns true if shift left or right is contained in the given set. + static bool _containsShift(Set keysPressed) { + return keysPressed.any({ LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftRight }.contains); + } + // Expand the selection to the given global position. // // Either base or extent will be moved to the last tapped position, whichever @@ -1994,15 +1999,6 @@ class TextSelectionGestureDetectorBuilder { /// The viewport offset pixels of the [RenderEditable] at the last drag start. double _dragStartViewportOffset = 0.0; - // Returns true iff either shift key is currently down. - bool get _isShiftPressed { - return HardwareKeyboard.instance.logicalKeysPressed - .any({ - LogicalKeyboardKey.shiftLeft, - LogicalKeyboardKey.shiftRight, - }.contains); - } - double get _scrollPosition { final ScrollableState? scrollableState = delegate.editableTextKey.currentContext == null @@ -2013,13 +2009,19 @@ class TextSelectionGestureDetectorBuilder { : scrollableState.position.pixels; } - // True iff a tap + shift has been detected but the tap has not yet come up. - bool _isShiftTapping = false; - // For a shift + tap + drag gesture, the TextSelection at the point of the // tap. Mac uses this value to reset to the original selection when an // inversion of the base and offset happens. - TextSelection? _shiftTapDragSelection; + TextSelection? _dragStartSelection; + + // For tap + drag gesture on iOS, whether the position where the drag started + // was on the previous TextSelection. iOS uses this value to determine if + // the cursor should move on drag update. + // + // If the drag started on the previous selection then the cursor will move on + // drag update. If the drag did not start on the previous selection then the + // cursor will not move on drag update. + bool? _dragBeganOnPreviousSelection; /// Handler for [TextSelectionGestureDetector.onTapDown]. /// @@ -2030,11 +2032,17 @@ class TextSelectionGestureDetectorBuilder { /// /// * [TextSelectionGestureDetector.onTapDown], which triggers this callback. @protected - void onTapDown(TapDownDetails details) { + void onTapDown(TapDragDownDetails details) { if (!delegate.selectionEnabled) { return; } - renderEditable.handleTapDown(details); + // TODO(Renzo-Olivares): Migrate text selection gestures away from saving state + // in renderEditable. The gesture callbacks can use the details objects directly + // in callbacks variants that provide them [TapGestureRecognizer.onSecondaryTap] + // vs [TapGestureRecognizer.onSecondaryTapUp] instead of having to track state in + // renderEditable. When this migration is complete we should remove this hack. + // See https://github.com/flutter/flutter/issues/115130. + renderEditable.handleTapDown(TapDownDetails(globalPosition: details.globalPosition)); // The selection overlay should only be shown when the user is interacting // through a touch screen (via either a finger or a stylus). A mouse shouldn't // trigger the selection overlay. @@ -2048,21 +2056,20 @@ class TextSelectionGestureDetectorBuilder { || kind == PointerDeviceKind.stylus; // Handle shift + click selection if needed. - final bool isShiftPressedValid = _isShiftPressed && renderEditable.selection?.baseOffset != null; + final bool isShiftPressed = _containsShift(details.keysPressedOnDown); + // It is impossible to extend the selection when the shift key is pressed, if the + // renderEditable.selection is invalid. + final bool isShiftPressedValid = isShiftPressed && renderEditable.selection?.baseOffset != null; switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: // On mobile platforms the selection is set on tap up. - if (_isShiftTapping) { - _isShiftTapping = false; - } break; case TargetPlatform.macOS: // On macOS, a shift-tapped unfocused field expands from 0, not from the // previous selection. if (isShiftPressedValid) { - _isShiftTapping = true; final TextSelection? fromSelection = renderEditable.hasFocus ? null : const TextSelection.collapsed(offset: 0); @@ -2082,7 +2089,6 @@ class TextSelectionGestureDetectorBuilder { case TargetPlatform.linux: case TargetPlatform.windows: if (isShiftPressedValid) { - _isShiftTapping = true; _extendSelection(details.globalPosition, SelectionChangedCause.tap); return; } @@ -2146,25 +2152,24 @@ class TextSelectionGestureDetectorBuilder { /// * [TextSelectionGestureDetector.onSingleTapUp], which triggers /// this callback. @protected - void onSingleTapUp(TapUpDetails details) { + void onSingleTapUp(TapDragUpDetails details) { if (delegate.selectionEnabled) { // Handle shift + click selection if needed. - final bool isShiftPressedValid = _isShiftPressed && renderEditable.selection?.baseOffset != null; + final bool isShiftPressed = _containsShift(details.keysPressedOnDown); + // It is impossible to extend the selection when the shift key is pressed, if the + // renderEditable.selection is invalid. + final bool isShiftPressedValid = isShiftPressed && renderEditable.selection?.baseOffset != null; switch (defaultTargetPlatform) { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: editableText.hideToolbar(); // On desktop platforms the selection is set on tap down. - if (_isShiftTapping) { - _isShiftTapping = false; - } break; case TargetPlatform.android: editableText.hideToolbar(); editableText.showSpellCheckSuggestionsToolbar(); if (isShiftPressedValid) { - _isShiftTapping = true; _extendSelection(details.globalPosition, SelectionChangedCause.tap); return; } @@ -2173,7 +2178,6 @@ class TextSelectionGestureDetectorBuilder { case TargetPlatform.fuchsia: editableText.hideToolbar(); if (isShiftPressedValid) { - _isShiftTapping = true; _extendSelection(details.globalPosition, SelectionChangedCause.tap); return; } @@ -2183,7 +2187,6 @@ class TextSelectionGestureDetectorBuilder { if (isShiftPressedValid) { // On iOS, a shift-tapped unfocused field expands from 0, not from // the previous selection. - _isShiftTapping = true; final TextSelection? fromSelection = renderEditable.hasFocus ? null : const TextSelection.collapsed(offset: 0); @@ -2246,7 +2249,7 @@ class TextSelectionGestureDetectorBuilder { /// * [TextSelectionGestureDetector.onSingleTapCancel], which triggers /// this callback. @protected - void onSingleTapCancel() {/* Subclass should override this method if needed. */} + void onSingleTapCancel() { /* Subclass should override this method if needed. */ } /// Handler for [TextSelectionGestureDetector.onSingleLongTapStart]. /// @@ -2416,7 +2419,13 @@ class TextSelectionGestureDetectorBuilder { /// * [onSecondaryTap], which is typically called after this. @protected void onSecondaryTapDown(TapDownDetails details) { - renderEditable.handleSecondaryTapDown(details); + // TODO(Renzo-Olivares): Migrate text selection gestures away from saving state + // in renderEditable. The gesture callbacks can use the details objects directly + // in callbacks variants that provide them [TapGestureRecognizer.onSecondaryTap] + // vs [TapGestureRecognizer.onSecondaryTapUp] instead of having to track state in + // renderEditable. When this migration is complete we should remove this hack. + // See https://github.com/flutter/flutter/issues/115130. + renderEditable.handleSecondaryTapDown(TapDownDetails(globalPosition: details.globalPosition)); _shouldShowSelectionToolbar = true; } @@ -2430,7 +2439,7 @@ class TextSelectionGestureDetectorBuilder { /// * [TextSelectionGestureDetector.onDoubleTapDown], which triggers this /// callback. @protected - void onDoubleTapDown(TapDownDetails details) { + void onDoubleTapDown(TapDragDownDetails details) { if (delegate.selectionEnabled) { renderEditable.selectWord(cause: SelectionChangedCause.tap); if (shouldShowSelectionToolbar) { @@ -2448,7 +2457,7 @@ class TextSelectionGestureDetectorBuilder { /// * [TextSelectionGestureDetector.onDragSelectionStart], which triggers /// this callback. @protected - void onDragSelectionStart(DragStartDetails details) { + void onDragSelectionStart(TapDragStartDetails details) { if (!delegate.selectionEnabled) { return; } @@ -2457,8 +2466,18 @@ class TextSelectionGestureDetectorBuilder { || kind == PointerDeviceKind.touch || kind == PointerDeviceKind.stylus; - if (_isShiftPressed && renderEditable.selection != null && renderEditable.selection!.isValid) { - _isShiftTapping = true; + _dragStartSelection = renderEditable.selection; + _dragStartScrollOffset = _scrollPosition; + _dragStartViewportOffset = renderEditable.offset.pixels; + + if (details.consecutiveTapCount > 1) { + // Do not set the selection on a consecutive tap and drag. + return; + } + + final bool isShiftPressed = _containsShift(details.keysPressedOnDown); + + if (isShiftPressed && renderEditable.selection != null && renderEditable.selection!.isValid) { switch (defaultTargetPlatform) { case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -2471,16 +2490,46 @@ class TextSelectionGestureDetectorBuilder { _extendSelection(details.globalPosition, SelectionChangedCause.drag); break; } - _shiftTapDragSelection = renderEditable.selection; } else { - renderEditable.selectPositionAt( - from: details.globalPosition, - cause: SelectionChangedCause.drag, - ); + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.android: + case TargetPlatform.fuchsia: + switch (details.kind) { + case PointerDeviceKind.mouse: + case PointerDeviceKind.trackpad: + case PointerDeviceKind.stylus: + case PointerDeviceKind.invertedStylus: + renderEditable.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + break; + case PointerDeviceKind.touch: + case PointerDeviceKind.unknown: + // For Android, Fucshia, and iOS platforms, a touch drag + // does not initiate unless the editable has focus. + if (renderEditable.hasFocus) { + renderEditable.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + } + break; + case null: + break; + } + break; + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + renderEditable.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + break; + } } - - _dragStartScrollOffset = _scrollPosition; - _dragStartViewportOffset = renderEditable.offset.pixels; } /// Handler for [TextSelectionGestureDetector.onDragSelectionUpdate]. @@ -2493,12 +2542,14 @@ class TextSelectionGestureDetectorBuilder { /// * [TextSelectionGestureDetector.onDragSelectionUpdate], which triggers /// this callback./lib/src/material/text_field.dart @protected - void onDragSelectionUpdate(DragStartDetails startDetails, DragUpdateDetails updateDetails) { + void onDragSelectionUpdate(TapDragUpdateDetails details) { if (!delegate.selectionEnabled) { return; } - if (!_isShiftTapping) { + final bool isShiftPressed = _containsShift(details.keysPressedOnDown); + + if (!isShiftPressed) { // Adjust the drag start offset for possible viewport offset changes. final Offset editableOffset = renderEditable.maxLines == 1 ? Offset(renderEditable.offset.pixels - _dragStartViewportOffset, 0.0) @@ -2507,52 +2558,131 @@ class TextSelectionGestureDetectorBuilder { 0.0, _scrollPosition - _dragStartScrollOffset, ); - return renderEditable.selectPositionAt( - from: startDetails.globalPosition - editableOffset - scrollableOffset, - to: updateDetails.globalPosition, - cause: SelectionChangedCause.drag, - ); + final Offset dragStartGlobalPosition = details.globalPosition - details.offsetFromOrigin; + + // Select word by word. + if (details.consecutiveTapCount == 2) { + return renderEditable.selectWordsInRange( + from: dragStartGlobalPosition - editableOffset - scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + } + + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + // With a touch device, nothing should happen, unless there was a double tap, or + // there was a collapsed selection, and the tap/drag position is at the collapsed selection. + // In that case the caret should move with the drag position. + // + // With a mouse device, a drag should select the range from the origin of the drag + // to the current position of the drag. + switch (details.kind) { + case PointerDeviceKind.mouse: + case PointerDeviceKind.trackpad: + return renderEditable.selectPositionAt( + from: dragStartGlobalPosition - editableOffset - scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + case PointerDeviceKind.stylus: + case PointerDeviceKind.invertedStylus: + case PointerDeviceKind.touch: + case PointerDeviceKind.unknown: + _dragBeganOnPreviousSelection ??= _positionOnSelection(dragStartGlobalPosition, _dragStartSelection); + assert(_dragBeganOnPreviousSelection != null); + if (renderEditable.hasFocus + && _dragStartSelection!.isCollapsed + && _dragBeganOnPreviousSelection! + ) { + return renderEditable.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + } + break; + case null: + break; + } + return; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + // With a precise pointer device, such as a mouse, trackpad, or stylus, + // the drag will select the text spanning the origin of the drag to the end of the drag. + // With a touch device, the cursor should move with the drag. + switch (details.kind) { + case PointerDeviceKind.mouse: + case PointerDeviceKind.trackpad: + case PointerDeviceKind.stylus: + case PointerDeviceKind.invertedStylus: + return renderEditable.selectPositionAt( + from: dragStartGlobalPosition - editableOffset - scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + case PointerDeviceKind.touch: + case PointerDeviceKind.unknown: + if (renderEditable.hasFocus) { + return renderEditable.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + } + break; + case null: + break; + } + return; + case TargetPlatform.macOS: + case TargetPlatform.linux: + case TargetPlatform.windows: + return renderEditable.selectPositionAt( + from: dragStartGlobalPosition - editableOffset - scrollableOffset, + to: details.globalPosition, + cause: SelectionChangedCause.drag, + ); + } } - if (_shiftTapDragSelection!.isCollapsed + if (_dragStartSelection!.isCollapsed || (defaultTargetPlatform != TargetPlatform.iOS && defaultTargetPlatform != TargetPlatform.macOS)) { - return _extendSelection(updateDetails.globalPosition, SelectionChangedCause.drag); + return _extendSelection(details.globalPosition, SelectionChangedCause.drag); } // If the drag inverts the selection, Mac and iOS revert to the initial // selection. final TextSelection selection = editableText.textEditingValue.selection; - final TextPosition nextExtent = renderEditable.getPositionForPoint(updateDetails.globalPosition); + final TextPosition nextExtent = renderEditable.getPositionForPoint(details.globalPosition); final bool isShiftTapDragSelectionForward = - _shiftTapDragSelection!.baseOffset < _shiftTapDragSelection!.extentOffset; + _dragStartSelection!.baseOffset < _dragStartSelection!.extentOffset; final bool isInverted = isShiftTapDragSelectionForward - ? nextExtent.offset < _shiftTapDragSelection!.baseOffset - : nextExtent.offset > _shiftTapDragSelection!.baseOffset; - if (isInverted && selection.baseOffset == _shiftTapDragSelection!.baseOffset) { + ? nextExtent.offset < _dragStartSelection!.baseOffset + : nextExtent.offset > _dragStartSelection!.baseOffset; + if (isInverted && selection.baseOffset == _dragStartSelection!.baseOffset) { editableText.userUpdateTextEditingValue( editableText.textEditingValue.copyWith( selection: TextSelection( - baseOffset: _shiftTapDragSelection!.extentOffset, + baseOffset: _dragStartSelection!.extentOffset, extentOffset: nextExtent.offset, ), ), SelectionChangedCause.drag, ); } else if (!isInverted - && nextExtent.offset != _shiftTapDragSelection!.baseOffset - && selection.baseOffset != _shiftTapDragSelection!.baseOffset) { + && nextExtent.offset != _dragStartSelection!.baseOffset + && selection.baseOffset != _dragStartSelection!.baseOffset) { editableText.userUpdateTextEditingValue( editableText.textEditingValue.copyWith( selection: TextSelection( - baseOffset: _shiftTapDragSelection!.baseOffset, + baseOffset: _dragStartSelection!.baseOffset, extentOffset: nextExtent.offset, ), ), SelectionChangedCause.drag, ); } else { - _extendSelection(updateDetails.globalPosition, SelectionChangedCause.drag); + _extendSelection(details.globalPosition, SelectionChangedCause.drag); } } @@ -2566,10 +2696,12 @@ class TextSelectionGestureDetectorBuilder { /// * [TextSelectionGestureDetector.onDragSelectionEnd], which triggers this /// callback. @protected - void onDragSelectionEnd(DragEndDetails details) { - if (_isShiftTapping) { - _isShiftTapping = false; - _shiftTapDragSelection = null; + void onDragSelectionEnd(TapDragEndDetails details) { + final bool isShiftPressed = _containsShift(details.keysPressedOnDown); + _dragBeganOnPreviousSelection = null; + + if (isShiftPressed) { + _dragStartSelection = null; } } @@ -2608,8 +2740,8 @@ class TextSelectionGestureDetectorBuilder { /// /// An ordinary [GestureDetector] configured to handle events like tap and /// double tap will only recognize one or the other. This widget detects both: -/// first the tap and then, if another tap down occurs within a time limit, the -/// double tap. +/// the first tap and then any subsequent taps that occurs within a time limit +/// after the first. /// /// See also: /// @@ -2644,7 +2776,7 @@ class TextSelectionGestureDetector extends StatefulWidget { /// Called for every tap down including every tap down that's part of a /// double click or a long press, except touches that include enough movement /// to not qualify as taps (e.g. pans and flings). - final GestureTapDownCallback? onTapDown; + final GestureTapDragDownCallback? onTapDown; /// Called when a pointer has tapped down and the force of the pointer has /// just become greater than [ForcePressGestureRecognizer.startPressure]. @@ -2660,16 +2792,18 @@ class TextSelectionGestureDetector extends StatefulWidget { /// Called for a tap down event with the secondary mouse button. final GestureTapDownCallback? onSecondaryTapDown; - /// Called for each distinct tap except for every second tap of a double tap. + /// Called for the first tap in a series of taps, consecutive taps do not call + /// this method. + /// /// For example, if the detector was configured with [onTapDown] and /// [onDoubleTapDown], three quick taps would be recognized as a single tap - /// down, followed by a double tap down, followed by a single tap down. - final GestureTapUpCallback? onSingleTapUp; + /// down, followed by a tap up, then a double tap down, followed by a single tap down. + final GestureTapDragUpCallback? onSingleTapUp; /// Called for each touch that becomes recognized as a gesture that is not a /// short tap, such as a long tap or drag. It is called at the moment when /// another gesture from the touch is recognized. - final GestureTapCancelCallback? onSingleTapCancel; + final GestureCancelCallback? onSingleTapCancel; /// Called for a single long tap that's sustained for longer than /// [kLongPressTimeout] but not necessarily lifted. Not called for a @@ -2684,20 +2818,20 @@ class TextSelectionGestureDetector extends StatefulWidget { /// Called after a momentary hold or a short tap that is close in space and /// time (within [kDoubleTapTimeout]) to a previous short tap. - final GestureTapDownCallback? onDoubleTapDown; + final GestureTapDragDownCallback? onDoubleTapDown; /// Called when a mouse starts dragging to select text. - final GestureDragStartCallback? onDragSelectionStart; + final GestureTapDragStartCallback? onDragSelectionStart; /// Called repeatedly as a mouse moves while dragging. /// /// The frequency of calls is throttled to avoid excessive text layout /// operations in text fields. The throttling is controlled by the constant /// [_kDragSelectionUpdateThrottle]. - final DragSelectionUpdateCallback? onDragSelectionUpdate; + final GestureTapDragUpdateCallback? onDragSelectionUpdate; /// Called when a mouse that was previously dragging is released. - final GestureDragEndCallback? onDragSelectionEnd; + final GestureTapDragEndCallback? onDragSelectionEnd; /// How this gesture detector should behave during hit testing. /// @@ -2712,100 +2846,50 @@ class TextSelectionGestureDetector extends StatefulWidget { } class _TextSelectionGestureDetectorState extends State { - // Counts down for a short duration after a previous tap. Null otherwise. - Timer? _doubleTapTimer; - Offset? _lastTapOffset; - // True if a second tap down of a double tap is detected. Used to discard - // subsequent tap up / tap hold of the same tap. - bool _isDoubleTap = false; + static int? _getDefaultMaxConsecutiveTap() => 2; @override void dispose() { - _doubleTapTimer?.cancel(); - _dragUpdateThrottleTimer?.cancel(); super.dispose(); } // The down handler is force-run on success of a single tap and optimistically // run before a long press success. - void _handleTapDown(TapDownDetails details) { + void _handleTapDown(TapDragDownDetails details) { widget.onTapDown?.call(details); // This isn't detected as a double tap gesture in the gesture recognizer // because it's 2 single taps, each of which may do different things depending // on whether it's a single tap, the first tap of a double tap, the second // tap held down, a clean double tap etc. - if (_doubleTapTimer != null && _isWithinDoubleTapTolerance(details.globalPosition)) { - // If there was already a previous tap, the second down hold/tap is a - // double tap down. - widget.onDoubleTapDown?.call(details); - _doubleTapTimer!.cancel(); - _doubleTapTimeout(); - _isDoubleTap = true; + if (details.consecutiveTapCount == 2) { + widget.onDoubleTapDown?.call(details); } } - void _handleTapUp(TapUpDetails details) { - if (!_isDoubleTap) { + void _handleTapUp(TapDragUpDetails details) { + if (details.consecutiveTapCount == 1) { widget.onSingleTapUp?.call(details); - _lastTapOffset = details.globalPosition; - _doubleTapTimer?.cancel(); - _doubleTapTimer = Timer(kDoubleTapTimeout, _doubleTapTimeout); } - _isDoubleTap = false; } void _handleTapCancel() { widget.onSingleTapCancel?.call(); } - DragStartDetails? _lastDragStartDetails; - DragUpdateDetails? _lastDragUpdateDetails; - Timer? _dragUpdateThrottleTimer; - - void _handleDragStart(DragStartDetails details) { - assert(_lastDragStartDetails == null); - _lastDragStartDetails = details; + void _handleDragStart(TapDragStartDetails details) { widget.onDragSelectionStart?.call(details); } - void _handleDragUpdate(DragUpdateDetails details) { - _lastDragUpdateDetails = details; - // Only schedule a new timer if there's no one pending. - _dragUpdateThrottleTimer ??= Timer(_kDragSelectionUpdateThrottle, _handleDragUpdateThrottled); + void _handleDragUpdate(TapDragUpdateDetails details) { + widget.onDragSelectionUpdate?.call(details); } - /// Drag updates are being throttled to avoid excessive text layouts in text - /// fields. The frequency of invocations is controlled by the constant - /// [_kDragSelectionUpdateThrottle]. - /// - /// Once the drag gesture ends, any pending drag update will be fired - /// immediately. See [_handleDragEnd]. - void _handleDragUpdateThrottled() { - assert(_lastDragStartDetails != null); - assert(_lastDragUpdateDetails != null); - widget.onDragSelectionUpdate?.call(_lastDragStartDetails!, _lastDragUpdateDetails!); - _dragUpdateThrottleTimer = null; - _lastDragUpdateDetails = null; - } - - void _handleDragEnd(DragEndDetails details) { - assert(_lastDragStartDetails != null); - if (_dragUpdateThrottleTimer != null) { - // If there's already an update scheduled, trigger it immediately and - // cancel the timer. - _dragUpdateThrottleTimer!.cancel(); - _handleDragUpdateThrottled(); - } + void _handleDragEnd(TapDragEndDetails details) { widget.onDragSelectionEnd?.call(details); - _dragUpdateThrottleTimer = null; - _lastDragStartDetails = null; - _lastDragUpdateDetails = null; } void _forcePressStarted(ForcePressDetails details) { - _doubleTapTimer?.cancel(); - _doubleTapTimer = null; widget.onForcePressStart?.call(details); } @@ -2814,37 +2898,21 @@ class _TextSelectionGestureDetectorState extends State( - () => LongPressGestureRecognizer(debugOwner: this, kind: PointerDeviceKind.touch), + () => LongPressGestureRecognizer(debugOwner: this, supportedDevices: { PointerDeviceKind.touch }), (LongPressGestureRecognizer instance) { instance ..onLongPressStart = _handleLongPressStart @@ -2880,16 +2945,21 @@ class _TextSelectionGestureDetectorState extends State( - () => PanGestureRecognizer(debugOwner: this, supportedDevices: { PointerDeviceKind.mouse }), - (PanGestureRecognizer instance) { + gestures[TapAndDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers( + () => TapAndDragGestureRecognizer(debugOwner: this), + (TapAndDragGestureRecognizer instance) { instance // Text selection should start from the position of the first pointer // down event. ..dragStartBehavior = DragStartBehavior.down - ..onStart = _handleDragStart - ..onUpdate = _handleDragUpdate - ..onEnd = _handleDragEnd; + ..dragUpdateThrottleFrequency = _kDragSelectionUpdateThrottle + ..maxConsecutiveTap = _getDefaultMaxConsecutiveTap() + ..onTapDown = _handleTapDown + ..onDragStart = _handleDragStart + ..onDragUpdate = _handleDragUpdate + ..onDragEnd = _handleDragEnd + ..onTapUp = _handleTapUp + ..onCancel = _handleTapCancel; }, ); } diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart index fbacdec14a82e..3eee850c7becc 100644 --- a/packages/flutter/lib/widgets.dart +++ b/packages/flutter/lib/widgets.dart @@ -135,6 +135,7 @@ export 'src/widgets/spacer.dart'; export 'src/widgets/spell_check.dart'; export 'src/widgets/status_transitions.dart'; export 'src/widgets/table.dart'; +export 'src/widgets/tap_and_drag_gestures.dart'; export 'src/widgets/tap_region.dart'; export 'src/widgets/text.dart'; export 'src/widgets/text_editing_intents.dart'; diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 69820d83fecf5..879fc694daaed 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -2125,6 +2125,105 @@ void main() { }, ); + testWidgets( + 'Can double click + drag with a mouse to select word by word', + (WidgetTester tester) async { + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + CupertinoApp( + home: CupertinoPageScaffold( + child: CupertinoTextField( + dragStartBehavior: DragStartBehavior.down, + controller: controller, + ), + ), + ), + ); + + const String testValue = 'abc def ghi'; + await tester.enterText(find.byType(CupertinoTextField), testValue); + await tester.pumpAndSettle(const Duration(milliseconds: 200)); + + final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e')); + final Offset hPos = textOffsetToPosition(tester, testValue.indexOf('h')); + + // Tap on text field to gain focus, and set selection to '|e'. + final TestGesture gesture = await tester.startGesture(ePos, kind: PointerDeviceKind.mouse); + await tester.pump(); + await gesture.up(); + await tester.pump(); + expect(controller.selection.isCollapsed, true); + expect(controller.selection.baseOffset, testValue.indexOf('e')); + + // Here we tap on '|e' again, to register a double tap. This will select + // the word at the tapped position. + await gesture.down(ePos); + await tester.pump(); + + expect(controller.selection.baseOffset, 4); + expect(controller.selection.extentOffset, 7); + + // Drag, right after the double tap, to select word by word. + // Moving to the position of 'h', will extend the selection to 'ghi'. + await gesture.moveTo(hPos); + await tester.pumpAndSettle(); + + expect(controller.selection.baseOffset, testValue.indexOf('d')); + expect(controller.selection.extentOffset, testValue.indexOf('i') + 1); + }, + ); + + testWidgets( + 'Can double tap + drag to select word by word', + (WidgetTester tester) async { + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + CupertinoApp( + home: CupertinoPageScaffold( + child: CupertinoTextField( + dragStartBehavior: DragStartBehavior.down, + controller: controller, + ), + ), + ), + ); + + const String testValue = 'abc def ghi'; + await tester.enterText(find.byType(CupertinoTextField), testValue); + await tester.pumpAndSettle(const Duration(milliseconds: 200)); + + final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e')); + final Offset hPos = textOffsetToPosition(tester, testValue.indexOf('h')); + + // Tap on text field to gain focus, and set selection to '|e'. + final TestGesture gesture = await tester.startGesture(ePos); + await tester.pump(); + await gesture.up(); + await tester.pump(); + + expect(controller.selection.isCollapsed, true); + expect(controller.selection.baseOffset, testValue.indexOf('e')); + + // Here we tap on '|e' again, to register a double tap. This will select + // the word at the tapped position. + await gesture.down(ePos); + await tester.pumpAndSettle(); + + expect(controller.selection.baseOffset, 4); + expect(controller.selection.extentOffset, 7); + + // Drag, right after the double tap, to select word by word. + // Moving to the position of 'h', will extend the selection to 'ghi'. + await gesture.moveTo(hPos); + await tester.pumpAndSettle(); + + expect(controller.selection.baseOffset, testValue.indexOf('d')); + expect(controller.selection.extentOffset, testValue.indexOf('i') + 1); + }, + ); + testWidgets('Readonly text field does not have tap action', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); @@ -3451,7 +3550,7 @@ void main() { // The selection doesn't move beyond the left handle. There's always at // least 1 char selected. expect(controller.selection.extentOffset, 5); - }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); + }, variant: const TargetPlatformVariant({ TargetPlatform.iOS })); testWidgets('Dragging between multiple lines keeps the contact point at the same place on the handle on Android', (WidgetTester tester) async { final TextEditingController controller = TextEditingController( @@ -3602,7 +3701,9 @@ void main() { renderEditable, ); handlePos = endpoints[0].point + startHandleAdjustment; - newHandlePos = handlePos - toNextLine; + // Move handle a sufficient global distance so it can be considered a drag + // by the selection handle's [PanGestureRecognizer]. + newHandlePos = handlePos - (toNextLine * 2); gesture = await tester.startGesture(handlePos, pointer: 7); await tester.pump(); await gesture.moveTo(newHandlePos); @@ -3626,6 +3727,12 @@ void main() { handlePos = endpoints[0].point + startHandleAdjustment; newHandlePos = handlePos + toNextLine; gesture = await tester.startGesture(handlePos, pointer: 7); + // Move handle up a small amount before dragging it down so the total global + // distance travelled can be accepted by the selection handle's [PanGestureRecognizer] as a drag. + // This way it can declare itself the winner before the [TapAndDragGestureRecognizer] that + // is on the selection overlay. + await tester.pump(); + await gesture.moveTo(handlePos - toNextLine); await tester.pump(); await gesture.moveTo(newHandlePos); await tester.pump(); @@ -3967,6 +4074,97 @@ void main() { expect(controller.selection.extentOffset, testValue.indexOf('g')); }); + testWidgets('Can move cursor when dragging, when tap is on collapsed selection (iOS)', (WidgetTester tester) async { + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + CupertinoApp( + home: CupertinoPageScaffold( + child: CupertinoTextField( + dragStartBehavior: DragStartBehavior.down, + controller: controller, + ), + ), + ), + ); + + const String testValue = 'abc def ghi'; + await tester.enterText(find.byType(CupertinoTextField), testValue); + await tester.pumpAndSettle(const Duration(milliseconds: 200)); + + final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e')); + final Offset iPos = textOffsetToPosition(tester, testValue.indexOf('i')); + + // Tap on text field to gain focus, and set selection to '|g'. On iOS + // the selection is set to the word edge closest to the tap position. + // We await for [kDoubleTapTimeout] after the up event, so our next down + // event does not register as a double tap. + final TestGesture gesture = await tester.startGesture(ePos); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(kDoubleTapTimeout); + + expect(controller.selection.isCollapsed, true); + expect(controller.selection.baseOffset, 7); + + // If the position we tap during a drag start is on the collapsed selection, then + // we can move the cursor with a drag. + // Here we tap on '|g', where our selection was previously, and move to '|i'. + await gesture.down(textOffsetToPosition(tester, 7)); + await tester.pump(); + await gesture.moveTo(iPos); + await tester.pumpAndSettle(); + + expect(controller.selection.isCollapsed, true); + expect(controller.selection.baseOffset, testValue.indexOf('i')); + }, + variant: const TargetPlatformVariant({ TargetPlatform.iOS }), + ); + + testWidgets('Can move cursor when dragging (Android)', (WidgetTester tester) async { + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + CupertinoApp( + home: CupertinoPageScaffold( + child: CupertinoTextField( + dragStartBehavior: DragStartBehavior.down, + controller: controller, + ), + ), + ), + ); + + const String testValue = 'abc def ghi'; + await tester.enterText(find.byType(CupertinoTextField), testValue); + await tester.pumpAndSettle(const Duration(milliseconds: 200)); + + final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e')); + final Offset gPos = textOffsetToPosition(tester, testValue.indexOf('g')); + + // Tap on text field to gain focus, and set selection to '|e'. + // We await for [kDoubleTapTimeout] after the up event, so our + // next down event does not register as a double tap. + final TestGesture gesture = await tester.startGesture(ePos); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(kDoubleTapTimeout); + + expect(controller.selection.isCollapsed, true); + expect(controller.selection.baseOffset, testValue.indexOf('e')); + + // Here we tap on '|d', and move to '|g'. + await gesture.down(textOffsetToPosition(tester, testValue.indexOf('d'))); + await tester.pump(); + await gesture.moveTo(gPos); + await tester.pumpAndSettle(); + + expect(controller.selection.isCollapsed, true); + expect(controller.selection.baseOffset, testValue.indexOf('g')); + }, + variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.fuchsia }), + ); + testWidgets('Continuous dragging does not cause flickering', (WidgetTester tester) async { int selectionChangedCount = 0; const String testValue = 'abc def ghi'; @@ -4400,7 +4598,7 @@ void main() { ); await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0)); await tester.pump(); - await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero + await tester.pump(const Duration(milliseconds: 300)); // skip past the frame where the opacity is zero // Verify the selection toolbar position Offset toolbarTopLeft = tester.getTopLeft(find.text('Paste')); @@ -6299,16 +6497,19 @@ void main() { pointer: 7, kind: PointerDeviceKind.mouse, ); + await tester.pumpAndSettle(); if (isTargetPlatformMobile) { await gesture.up(); + // Not a double tap + drag. + await tester.pumpAndSettle(kDoubleTapTimeout); } - await tester.pumpAndSettle(); expect(controller.selection.baseOffset, 8); expect(controller.selection.extentOffset, 23); // Expand the selection a bit. if (isTargetPlatformMobile) { await gesture.down(textOffsetToPosition(tester, 24)); + await tester.pumpAndSettle(); } await gesture.moveTo(textOffsetToPosition(tester, 28)); await tester.pumpAndSettle(); @@ -6403,16 +6604,19 @@ void main() { pointer: 7, kind: PointerDeviceKind.mouse, ); + await tester.pumpAndSettle(); if (isTargetPlatformMobile) { await gesture.up(); + // Not a double tap + drag. + await tester.pumpAndSettle(kDoubleTapTimeout); } - await tester.pumpAndSettle(); expect(controller.selection.baseOffset, 8); expect(controller.selection.extentOffset, 23); // Expand the selection a bit. if (isTargetPlatformMobile) { await gesture.down(textOffsetToPosition(tester, 24)); + await tester.pumpAndSettle(); } await gesture.moveTo(textOffsetToPosition(tester, 28)); await tester.pumpAndSettle(); @@ -6506,8 +6710,11 @@ void main() { pointer: 7, kind: PointerDeviceKind.mouse, ); + await tester.pumpAndSettle(); if (isTargetPlatformMobile) { await gesture.up(); + // Not a double tap + drag. + await tester.pumpAndSettle(kDoubleTapTimeout); } await tester.pumpAndSettle(); expect(controller.selection.baseOffset, 23); @@ -6516,6 +6723,7 @@ void main() { // Expand the selection a bit. if (isTargetPlatformMobile) { await gesture.down(textOffsetToPosition(tester, 7)); + await tester.pumpAndSettle(); } await gesture.moveTo(textOffsetToPosition(tester, 5)); await tester.pumpAndSettle(); @@ -6610,16 +6818,19 @@ void main() { pointer: 7, kind: PointerDeviceKind.mouse, ); + await tester.pumpAndSettle(); if (isTargetPlatformMobile) { await gesture.up(); + // Not a double tap + drag. + await tester.pumpAndSettle(kDoubleTapTimeout); } - await tester.pumpAndSettle(); expect(controller.selection.baseOffset, 23); expect(controller.selection.extentOffset, 8); // Expand the selection a bit. if (isTargetPlatformMobile) { await gesture.down(textOffsetToPosition(tester, 7)); + await tester.pumpAndSettle(); } await gesture.moveTo(textOffsetToPosition(tester, 5)); await tester.pumpAndSettle(); diff --git a/packages/flutter/test/gestures/tap_test.dart b/packages/flutter/test/gestures/tap_test.dart index 7001509f8b00d..e54dc8ed685bf 100644 --- a/packages/flutter/test/gestures/tap_test.dart +++ b/packages/flutter/test/gestures/tap_test.dart @@ -313,7 +313,6 @@ void main() { tester.route(down1); expect(tapsRecognized, 0); - tester.route(up2); expect(tapsRecognized, 0); GestureBinding.instance.gestureArena.sweep(2); diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index ea3ba40632148..307f7f1dce32a 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -2135,6 +2135,97 @@ void main() { expect(controller.selection.extentOffset, testValue.indexOf('g')); }); + testWidgets('Can move cursor when dragging, when tap is on collapsed selection (iOS)', (WidgetTester tester) async { + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: TextField( + dragStartBehavior: DragStartBehavior.down, + controller: controller, + ), + ), + ), + ); + + const String testValue = 'abc def ghi'; + await tester.enterText(find.byType(TextField), testValue); + await skipPastScrollingAnimation(tester); + + final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e')); + final Offset iPos = textOffsetToPosition(tester, testValue.indexOf('i')); + + // Tap on text field to gain focus, and set selection to '|g'. On iOS + // the selection is set to the word edge closest to the tap position. + // We await for 300ms after the up event, so our next down event does not + // register as a double tap. + final TestGesture gesture = await tester.startGesture(ePos); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(kDoubleTapTimeout); + + expect(controller.selection.isCollapsed, true); + expect(controller.selection.baseOffset, 7); + + // If the position we tap during a drag start is on the collapsed selection, then + // we can move the cursor with a drag. + // Here we tap on '|g', where our selection was previously, and move to '|i'. + await gesture.down(textOffsetToPosition(tester, 7)); + await tester.pump(); + await gesture.moveTo(iPos); + await tester.pumpAndSettle(); + + expect(controller.selection.isCollapsed, true); + expect(controller.selection.baseOffset, testValue.indexOf('i')); + }, + variant: const TargetPlatformVariant({ TargetPlatform.iOS }), + ); + + testWidgets('Can move cursor when dragging (Android)', (WidgetTester tester) async { + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: TextField( + dragStartBehavior: DragStartBehavior.down, + controller: controller, + ), + ), + ), + ); + + const String testValue = 'abc def ghi'; + await tester.enterText(find.byType(TextField), testValue); + await skipPastScrollingAnimation(tester); + + final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e')); + final Offset gPos = textOffsetToPosition(tester, testValue.indexOf('g')); + + // Tap on text field to gain focus, and set selection to '|e'. + // We await for 300ms after the up event, so our next down event does not + // register as a double tap. + final TestGesture gesture = await tester.startGesture(ePos); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(kDoubleTapTimeout); + + expect(controller.selection.isCollapsed, true); + expect(controller.selection.baseOffset, testValue.indexOf('e')); + + // Here we tap on '|d', and move to '|g'. + await gesture.down(textOffsetToPosition(tester, testValue.indexOf('d'))); + await tester.pump(); + await gesture.moveTo(gPos); + await tester.pumpAndSettle(); + + expect(controller.selection.isCollapsed, true); + expect(controller.selection.baseOffset, testValue.indexOf('g')); + }, + variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.fuchsia }), + ); + testWidgets('Continuous dragging does not cause flickering', (WidgetTester tester) async { int selectionChangedCount = 0; const String testValue = 'abc def ghi'; @@ -2979,7 +3070,9 @@ void main() { renderEditable, ); handlePos = endpoints[0].point + startHandleAdjustment; - newHandlePos = handlePos - toNextLine; + // Move handle a sufficient global distance so it can be considered a drag + // by the selection handle's [PanGestureRecognizer]. + newHandlePos = handlePos - (toNextLine * 2); gesture = await tester.startGesture(handlePos, pointer: 7); await tester.pump(); await gesture.moveTo(newHandlePos); @@ -3004,6 +3097,12 @@ void main() { newHandlePos = handlePos + toNextLine; gesture = await tester.startGesture(handlePos, pointer: 7); await tester.pump(); + // Move handle up a small amount before dragging it down so the total global + // distance travelled can be accepted by the selection handle's [PanGestureRecognizer] as a drag. + // This way it can declare itself the winner before the [TapAndDragGestureRecognizer] that + // is on the selection overlay. + await gesture.moveTo(handlePos - toNextLine); + await tester.pump(); await gesture.moveTo(newHandlePos); await tester.pump(); await gesture.up(); @@ -8711,6 +8810,105 @@ void main() { expect(widget.selectionControls, equals(materialTextSelectionControls)); }); + testWidgets( + 'Can double click + drag with a mouse to select word by word', + (WidgetTester tester) async { + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: TextField( + dragStartBehavior: DragStartBehavior.down, + controller: controller, + ), + ), + ), + ); + + const String testValue = 'abc def ghi'; + await tester.enterText(find.byType(TextField), testValue); + await skipPastScrollingAnimation(tester); + + final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e')); + final Offset hPos = textOffsetToPosition(tester, testValue.indexOf('h')); + + // Tap on text field to gain focus, and set selection to '|e'. + final TestGesture gesture = await tester.startGesture(ePos, kind: PointerDeviceKind.mouse); + await tester.pump(); + await gesture.up(); + await tester.pump(); + expect(controller.selection.isCollapsed, true); + expect(controller.selection.baseOffset, testValue.indexOf('e')); + + // Here we tap on '|e' again, to register a double tap. This will select + // the word at the tapped position. + await gesture.down(ePos); + await tester.pump(); + + expect(controller.selection.baseOffset, 4); + expect(controller.selection.extentOffset, 7); + + // Drag, right after the double tap, to select word by word. + // Moving to the position of 'h', will extend the selection to 'ghi'. + await gesture.moveTo(hPos); + await tester.pumpAndSettle(); + + expect(controller.selection.baseOffset, testValue.indexOf('d')); + expect(controller.selection.extentOffset, testValue.indexOf('i') + 1); + }, + ); + + testWidgets( + 'Can double tap + drag to select word by word', + (WidgetTester tester) async { + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: TextField( + dragStartBehavior: DragStartBehavior.down, + controller: controller, + ), + ), + ), + ); + + const String testValue = 'abc def ghi'; + await tester.enterText(find.byType(TextField), testValue); + await skipPastScrollingAnimation(tester); + + final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e')); + final Offset hPos = textOffsetToPosition(tester, testValue.indexOf('h')); + + // Tap on text field to gain focus, and set selection to '|e'. + final TestGesture gesture = await tester.startGesture(ePos); + await tester.pump(); + await gesture.up(); + await tester.pump(); + + expect(controller.selection.isCollapsed, true); + expect(controller.selection.baseOffset, testValue.indexOf('e')); + + // Here we tap on '|e' again, to register a double tap. This will select + // the word at the tapped position. + await gesture.down(ePos); + await tester.pumpAndSettle(); + + expect(controller.selection.baseOffset, 4); + expect(controller.selection.extentOffset, 7); + + // Drag, right after the double tap, to select word by word. + // Moving to the position of 'h', will extend the selection to 'ghi'. + await gesture.moveTo(hPos); + await tester.pumpAndSettle(); + + expect(controller.selection.baseOffset, testValue.indexOf('d')); + expect(controller.selection.extentOffset, testValue.indexOf('i') + 1); + }, + ); + testWidgets( 'double tap on top of cursor also selects word', (WidgetTester tester) async { @@ -9983,7 +10181,7 @@ void main() { ); await tester.pump(); await gesture.up(); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(kDoubleTapTimeout); expect( controller.selection, const TextSelection.collapsed(offset: 3), @@ -10370,7 +10568,7 @@ void main() { expect(controller.value.selection.extentOffset, 1); }, variant: const TargetPlatformVariant({ TargetPlatform.macOS, TargetPlatform.windows, TargetPlatform.linux })); - testWidgets('force press does not select a word', (WidgetTester tester) async { + testWidgets('Force press does not set selection on Android or Fuchsia touch devices', (WidgetTester tester) async { final TextEditingController controller = TextEditingController( text: 'Atwater Peel Sherbrooke Bonaventure', ); @@ -10405,13 +10603,56 @@ void main() { pressureMin: 0, )); + await gesture.up(); + await tester.pump(); + // We don't want this gesture to select any word on Android. expect(controller.selection, const TextSelection.collapsed(offset: -1)); + expect(find.byType(TextButton), findsNothing); + }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.fuchsia })); + + testWidgets('Force press sets selection on desktop platforms that do not support it', (WidgetTester tester) async { + final TextEditingController controller = TextEditingController( + text: 'Atwater Peel Sherbrooke Bonaventure', + ); + await tester.pumpWidget( + MaterialApp( + home: Material( + child: TextField( + controller: controller, + ), + ), + ), + ); + + final Offset offset = tester.getTopLeft(find.byType(TextField)) + const Offset(150.0, 9.0); + + final int pointerValue = tester.nextPointer; + final TestGesture gesture = await tester.createGesture(); + await gesture.downWithCustomEvent( + offset, + PointerDownEvent( + pointer: pointerValue, + position: offset, + pressure: 0.0, + pressureMax: 6.0, + pressureMin: 0.0, + ), + ); + await gesture.updateWithCustomEvent(PointerMoveEvent( + pointer: pointerValue, + position: offset + const Offset(150.0, 9.0), + pressure: 0.5, + pressureMin: 0, + )); await gesture.up(); await tester.pump(); + + // We don't want this gesture to select any word on Android. + expect(controller.selection, const TextSelection.collapsed(offset: 9)); expect(find.byType(TextButton), findsNothing); - }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows })); + }, variant: const TargetPlatformVariant({ TargetPlatform.linux, TargetPlatform.windows })); testWidgets('force press selects word', (WidgetTester tester) async { final TextEditingController controller = TextEditingController( @@ -12697,16 +12938,19 @@ void main() { pointer: 7, kind: PointerDeviceKind.mouse, ); + await tester.pumpAndSettle(); if (isTargetPlatformMobile) { await gesture.up(); + // Not a double tap + drag. + await tester.pumpAndSettle(kDoubleTapTimeout); } - await tester.pumpAndSettle(); expect(controller.selection.baseOffset, 8); expect(controller.selection.extentOffset, 23); // Expand the selection a bit. if (isTargetPlatformMobile) { await gesture.down(textOffsetToPosition(tester, 23)); + await tester.pumpAndSettle(); } await gesture.moveTo(textOffsetToPosition(tester, 28)); await tester.pumpAndSettle(); @@ -12904,16 +13148,19 @@ void main() { pointer: 7, kind: PointerDeviceKind.mouse, ); + await tester.pumpAndSettle(); if (isTargetPlatformMobile) { await gesture.up(); + // Not a double tap + drag. + await tester.pumpAndSettle(kDoubleTapTimeout); } - await tester.pumpAndSettle(); expect(controller.selection.baseOffset, 23); expect(controller.selection.extentOffset, 8); // Expand the selection a bit. if (isTargetPlatformMobile) { await gesture.down(textOffsetToPosition(tester, 8)); + await tester.pumpAndSettle(); } await gesture.moveTo(textOffsetToPosition(tester, 5)); await tester.pumpAndSettle(); diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index 2a1a992a9662a..ce3bfde3c87ee 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -196,7 +196,7 @@ void main() { final TestGesture gesture = await tester.startGesture(textFieldStart, kind: PointerDeviceKind.mouse); await tester.pump(const Duration(seconds: 2)); await gesture.up(); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(kDoubleTapTimeout); final FlutterError error = tester.takeException() as FlutterError; expect( @@ -3907,6 +3907,7 @@ void main() { expect(find.byType(CupertinoButton), findsNWidgets(1)); // Double tap selecting the same word somewhere else is fine. + await tester.pumpAndSettle(kDoubleTapTimeout); await tester.tapAt(selectableTextStart + const Offset(10.0, 5.0)); await tester.pump(const Duration(milliseconds: 50)); // First tap moved the cursor. @@ -3928,6 +3929,7 @@ void main() { editableTextState.hideToolbar(); await tester.pumpAndSettle(); + await tester.pumpAndSettle(kDoubleTapTimeout); await tester.tapAt(selectableTextStart + const Offset(150.0, 5.0)); await tester.pump(const Duration(milliseconds: 50)); // First tap moved the cursor. diff --git a/packages/flutter/test/widgets/tap_and_drag_gestures_test.dart b/packages/flutter/test/widgets/tap_and_drag_gestures_test.dart new file mode 100644 index 0000000000000..32e50e3aeb211 --- /dev/null +++ b/packages/flutter/test/widgets/tap_and_drag_gestures_test.dart @@ -0,0 +1,552 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/gestures.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../gestures/gesture_tester.dart'; + +// Anything longer than [kDoubleTapTimeout] will reset the consecutive tap count. +final Duration kConsecutiveTapDelay = kDoubleTapTimeout ~/ 2; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late List events; + late TapAndDragGestureRecognizer tapAndDrag; + + setUp(() { + events = []; + tapAndDrag = TapAndDragGestureRecognizer() + ..dragStartBehavior = DragStartBehavior.down + ..maxConsecutiveTap = 3 + ..onTapDown = (TapDragDownDetails details) { + events.add('down#${details.consecutiveTapCount}'); + } + ..onTapUp = (TapDragUpDetails details) { + events.add('up#${details.consecutiveTapCount}'); + } + ..onDragStart = (TapDragStartDetails details) { + events.add('dragstart#${details.consecutiveTapCount}'); + } + ..onDragUpdate = (TapDragUpdateDetails details) { + events.add('dragupdate#${details.consecutiveTapCount}'); + } + ..onDragEnd = (TapDragEndDetails details) { + events.add('dragend#${details.consecutiveTapCount}'); + } + ..onCancel = () { + events.add('cancel'); + }; + }); + + // Down/up pair 1: normal tap sequence + const PointerDownEvent down1 = PointerDownEvent( + pointer: 1, + position: Offset(10.0, 10.0), + ); + + const PointerUpEvent up1 = PointerUpEvent( + pointer: 1, + position: Offset(11.0, 9.0), + ); + + const PointerCancelEvent cancel1 = PointerCancelEvent( + pointer: 1, + ); + + // Down/up pair 2: normal tap sequence close to pair 1 + const PointerDownEvent down2 = PointerDownEvent( + pointer: 2, + position: Offset(12.0, 12.0), + ); + + const PointerUpEvent up2 = PointerUpEvent( + pointer: 2, + position: Offset(13.0, 11.0), + ); + + // Down/up pair 3: normal tap sequence close to pair 1 + const PointerDownEvent down3 = PointerDownEvent( + pointer: 3, + position: Offset(12.0, 12.0), + ); + + const PointerUpEvent up3 = PointerUpEvent( + pointer: 3, + position: Offset(13.0, 11.0), + ); + + // Down/up pair 4: normal tap sequence far away from pair 1 + const PointerDownEvent down4 = PointerDownEvent( + pointer: 4, + position: Offset(130.0, 130.0), + ); + + const PointerUpEvent up4 = PointerUpEvent( + pointer: 4, + position: Offset(131.0, 129.0), + ); + + // Down/move/up sequence 5: intervening motion + const PointerDownEvent down5 = PointerDownEvent( + pointer: 5, + position: Offset(10.0, 10.0), + ); + + const PointerMoveEvent move5 = PointerMoveEvent( + pointer: 5, + position: Offset(25.0, 25.0), + ); + + const PointerUpEvent up5 = PointerUpEvent( + pointer: 5, + position: Offset(25.0, 25.0), + ); + + testGesture('Recognizes consecutive taps', (GestureTester tester) { + tapAndDrag.addPointer(down1); + tester.closeArena(1); + tester.route(down1); + tester.route(up1); + GestureBinding.instance.gestureArena.sweep(1); + expect(events, ['down#1', 'up#1']); + + events.clear(); + tester.async.elapse(kConsecutiveTapDelay); + tapAndDrag.addPointer(down2); + tester.closeArena(2); + tester.route(down2); + tester.route(up2); + GestureBinding.instance.gestureArena.sweep(2); + expect(events, ['down#2', 'up#2']); + + events.clear(); + tester.async.elapse(kConsecutiveTapDelay); + tapAndDrag.addPointer(down3); + tester.closeArena(3); + tester.route(down3); + tester.route(up3); + GestureBinding.instance.gestureArena.sweep(3); + expect(events, ['down#3', 'up#3']); + }); + + testGesture('Resets if times out in between taps', (GestureTester tester) { + tapAndDrag.addPointer(down1); + tester.closeArena(1); + tester.route(down1); + tester.route(up1); + GestureBinding.instance.gestureArena.sweep(1); + expect(events, ['down#1', 'up#1']); + + events.clear(); + tester.async.elapse(const Duration(milliseconds: 1000)); + tapAndDrag.addPointer(down2); + tester.closeArena(2); + tester.route(down2); + tester.route(up2); + GestureBinding.instance.gestureArena.sweep(2); + expect(events, ['down#1', 'up#1']); + }); + + testGesture('Resets if taps are far apart', (GestureTester tester) { + tapAndDrag.addPointer(down1); + tester.closeArena(1); + tester.route(down1); + tester.route(up1); + GestureBinding.instance.gestureArena.sweep(1); + expect(events, ['down#1', 'up#1']); + + events.clear(); + tester.async.elapse(const Duration(milliseconds: 100)); + tapAndDrag.addPointer(down4); + tester.closeArena(4); + tester.route(down4); + tester.route(up4); + GestureBinding.instance.gestureArena.sweep(4); + expect(events, ['down#1', 'up#1']); + }); + + testGesture('Resets if consecutiveTapCount reaches maxConsecutiveTap', (GestureTester tester) { + // First tap. + tapAndDrag.addPointer(down1); + tester.closeArena(1); + tester.route(down1); + tester.route(up1); + GestureBinding.instance.gestureArena.sweep(1); + expect(events, ['down#1', 'up#1']); + + // Second tap. + events.clear(); + tapAndDrag.addPointer(down2); + tester.closeArena(2); + tester.route(down2); + tester.route(up2); + GestureBinding.instance.gestureArena.sweep(2); + expect(events, ['down#2', 'up#2']); + + // Third tap. + events.clear(); + tapAndDrag.addPointer(down3); + tester.closeArena(3); + tester.route(down3); + tester.route(up3); + GestureBinding.instance.gestureArena.sweep(3); + expect(events, ['down#3', 'up#3']); + + // Fourth tap. Here we arrived at the `maxConsecutiveTap` for `consecutiveTapCount` + // so our count should reset and our new count should be `1`. + events.clear(); + tapAndDrag.addPointer(down3); + tester.closeArena(3); + tester.route(down3); + tester.route(up3); + GestureBinding.instance.gestureArena.sweep(3); + expect(events, ['down#1', 'up#1']); + }); + + testGesture('Should recognize drag', (GestureTester tester) { + final TestPointer pointer = TestPointer(5); + final PointerDownEvent down = pointer.down(const Offset(10.0, 10.0)); + tapAndDrag.addPointer(down); + tester.closeArena(5); + tester.route(down); + tester.route(pointer.move(const Offset(40.0, 45.0))); + tester.route(pointer.up()); + GestureBinding.instance.gestureArena.sweep(5); + expect(events, ['down#1', 'dragstart#1', 'dragupdate#1', 'dragend#1']); + }); + + testGesture('Recognizes consecutive taps + drag', (GestureTester tester) { + final TestPointer pointer = TestPointer(5); + final PointerDownEvent downA = pointer.down(const Offset(10.0, 10.0)); + tapAndDrag.addPointer(downA); + tester.closeArena(5); + tester.route(downA); + tester.route(pointer.up()); + GestureBinding.instance.gestureArena.sweep(5); + + tester.async.elapse(kConsecutiveTapDelay); + + final PointerDownEvent downB = pointer.down(const Offset(10.0, 10.0)); + tapAndDrag.addPointer(downB); + tester.closeArena(5); + tester.route(downB); + tester.route(pointer.up()); + GestureBinding.instance.gestureArena.sweep(5); + + tester.async.elapse(kConsecutiveTapDelay); + + final PointerDownEvent downC = pointer.down(const Offset(10.0, 10.0)); + tapAndDrag.addPointer(downC); + tester.closeArena(5); + tester.route(downC); + tester.route(pointer.move(const Offset(40.0, 45.0))); + tester.route(pointer.up()); + expect(events, [ + 'down#1', + 'up#1', + 'down#2', + 'up#2', + 'down#3', + 'dragstart#3', + 'dragupdate#3', + 'dragend#3']); + }); + + testGesture('Recognizer rejects pointer that is not the primary one (FIFO) - before acceptance', (GestureTester tester) { + tapAndDrag.addPointer(down1); + tapAndDrag.addPointer(down2); + tester.closeArena(1); + tester.route(down1); + + tester.closeArena(2); + tester.route(down2); + + tester.route(up1); + GestureBinding.instance.gestureArena.sweep(1); + + tester.route(up2); + GestureBinding.instance.gestureArena.sweep(2); + expect(events, ['down#1', 'up#1']); + }); + + testGesture('Calls tap up when the recognizer accepts before handleEvent is called', (GestureTester tester) { + tapAndDrag.addPointer(down1); + tester.closeArena(1); + GestureBinding.instance.gestureArena.sweep(1); + tester.route(down1); + tester.route(up1); + expect(events, ['down#1', 'up#1']); + }); + + testGesture('Recognizer rejects pointer that is not the primary one (FILO) - before acceptance', (GestureTester tester) { + tapAndDrag.addPointer(down1); + tapAndDrag.addPointer(down2); + tester.closeArena(1); + tester.route(down1); + + tester.closeArena(2); + tester.route(down2); + + tester.route(up2); + GestureBinding.instance.gestureArena.sweep(2); + + tester.route(up1); + GestureBinding.instance.gestureArena.sweep(1); + expect(events, ['down#1', 'up#1']); + }); + + testGesture('Recognizer rejects pointer that is not the primary one (FIFO) - after acceptance', (GestureTester tester) { + tapAndDrag.addPointer(down1); + tester.closeArena(1); + tester.route(down1); + + tapAndDrag.addPointer(down2); + tester.closeArena(2); + tester.route(down2); + + tester.route(up1); + GestureBinding.instance.gestureArena.sweep(1); + + tester.route(up2); + GestureBinding.instance.gestureArena.sweep(2); + + expect(events, ['down#1', 'up#1']); + }); + + testGesture('Recognizer rejects pointer that is not the primary one (FILO) - after acceptance', (GestureTester tester) { + tapAndDrag.addPointer(down1); + tester.closeArena(1); + tester.route(down1); + + tapAndDrag.addPointer(down2); + tester.closeArena(2); + tester.route(down2); + + tester.route(up2); + GestureBinding.instance.gestureArena.sweep(2); + + tester.route(up1); + GestureBinding.instance.gestureArena.sweep(1); + expect(events, ['down#1', 'up#1']); + }); + + testGesture('Recognizer detects tap gesture when pointer does not move past tap tolerance', (GestureTester tester) { + // In this test the tap has not travelled past the tap tolerance defined by + // [kDoubleTapTouchSlop]. It is expected for the recognizer to detect a tap + // and fire drag cancel. + tapAndDrag.addPointer(down1); + tester.closeArena(1); + tester.route(down1); + tester.route(up1); + GestureBinding.instance.gestureArena.sweep(1); + expect(events, ['down#1', 'up#1']); + }); + + testGesture('Recognizer detects drag gesture when pointer moves past tap tolerance but not the drag minimum', (GestureTester tester) { + // In this test, the pointer has moved past the tap tolerance but it has + // not reached the distance travelled to be considered a drag gesture. In + // this case it is expected for the recognizer to detect a drag and fire tap cancel. + tapAndDrag.addPointer(down5); + tester.closeArena(5); + tester.route(down5); + tester.route(move5); + tester.route(up5); + GestureBinding.instance.gestureArena.sweep(5); + expect(events, ['down#1', 'dragstart#1', 'dragend#1']); + }); + + testGesture('Recognizer loses when competing against a DragGestureRecognizer when the pointer travels minimum distance to be considered a drag', (GestureTester tester) { + final PanGestureRecognizer pans = PanGestureRecognizer() + ..onStart = (DragStartDetails details) { + events.add('panstart'); + } + ..onUpdate = (DragUpdateDetails details) { + events.add('panupdate'); + } + ..onEnd = (DragEndDetails details) { + events.add('panend'); + } + ..onCancel = () { + events.add('pancancel'); + }; + + final TestPointer pointer = TestPointer(5); + final PointerDownEvent downB = pointer.down(const Offset(10.0, 10.0)); + // When competing against another [DragGestureRecognizer], the [TapAndDragGestureRecognizer] + // will only win when it is the last recognizer in the arena. + tapAndDrag.addPointer(downB); + pans.addPointer(downB); + tester.closeArena(5); + tester.route(downB); + tester.route(pointer.move(const Offset(40.0, 45.0))); + tester.route(pointer.up()); + expect(events, [ + 'panstart', + 'panend']); + }); + + testGesture('Beats LongPressGestureRecognizer on a consecutive tap greater than one', (GestureTester tester) { + final LongPressGestureRecognizer longpress = LongPressGestureRecognizer() + ..onLongPressStart = (LongPressStartDetails details) { + events.add('longpressstart'); + } + ..onLongPressMoveUpdate = (LongPressMoveUpdateDetails details) { + events.add('longpressmoveupdate'); + } + ..onLongPressEnd = (LongPressEndDetails details) { + events.add('longpressend'); + } + ..onLongPressCancel = () { + events.add('longpresscancel'); + }; + + final TestPointer pointer = TestPointer(5); + final PointerDownEvent downA = pointer.down(const Offset(10.0, 10.0)); + tapAndDrag.addPointer(downA); + longpress.addPointer(downA); + tester.closeArena(5); + tester.route(downA); + tester.route(pointer.up()); + GestureBinding.instance.gestureArena.sweep(5); + + tester.async.elapse(kConsecutiveTapDelay); + + final PointerDownEvent downB = pointer.down(const Offset(10.0, 10.0)); + tapAndDrag.addPointer(downB); + longpress.addPointer(downB); + tester.closeArena(5); + tester.route(downB); + + tester.async.elapse(const Duration(milliseconds: 500)); + + tester.route(pointer.move(const Offset(40.0, 45.0))); + tester.route(pointer.up()); + expect(events, [ + 'longpresscancel', + 'down#1', + 'up#1', + 'down#2', + 'dragstart#2', + 'dragupdate#2', + 'dragend#2']); + }); + + testGesture('Beats TapGestureRecognizer when the pointer has not moved and this recognizer is the first in the arena', (GestureTester tester) { + final TapGestureRecognizer taps = TapGestureRecognizer() + ..onTapDown = (TapDownDetails details) { + events.add('tapdown'); + } + ..onTapUp = (TapUpDetails details) { + events.add('tapup'); + } + ..onTapCancel = () { + events.add('tapscancel'); + }; + + tapAndDrag.addPointer(down1); + taps.addPointer(down1); + tester.closeArena(1); + tester.route(down1); + tester.route(up1); + GestureBinding.instance.gestureArena.sweep(1); + expect(events, ['down#1', 'up#1']); + }); + + testGesture('Beats TapGestureRecognizer when the pointer has exceeded the slop tolerance', (GestureTester tester) { + final TapGestureRecognizer taps = TapGestureRecognizer() + ..onTapDown = (TapDownDetails details) { + events.add('tapdown'); + } + ..onTapUp = (TapUpDetails details) { + events.add('tapup'); + } + ..onTapCancel = () { + events.add('tapscancel'); + }; + + tapAndDrag.addPointer(down5); + taps.addPointer(down5); + tester.closeArena(5); + tester.route(down5); + tester.route(move5); + tester.route(up5); + GestureBinding.instance.gestureArena.sweep(5); + expect(events, ['down#1', 'dragstart#1', 'dragend#1']); + + events.clear(); + tester.async.elapse(const Duration(milliseconds: 1000)); + taps.addPointer(down1); + tapAndDrag.addPointer(down1); + tester.closeArena(1); + tester.route(down1); + tester.route(up1); + GestureBinding.instance.gestureArena.sweep(1); + expect(events, ['tapdown', 'tapup']); + }); + + testGesture('Ties with PanGestureRecognizer when pointer has not met sufficient global distance to be a drag', (GestureTester tester) { + final PanGestureRecognizer pans = PanGestureRecognizer() + ..onStart = (DragStartDetails details) { + events.add('panstart'); + } + ..onUpdate = (DragUpdateDetails details) { + events.add('panupdate'); + } + ..onEnd = (DragEndDetails details) { + events.add('panend'); + } + ..onCancel = () { + events.add('pancancel'); + }; + + tapAndDrag.addPointer(down5); + pans.addPointer(down5); + tester.closeArena(5); + tester.route(down5); + tester.route(move5); + tester.route(up5); + GestureBinding.instance.gestureArena.sweep(5); + expect(events, ['pancancel']); + }); + + testGesture('Defaults to drag when pointer dragged past slop tolerance', (GestureTester tester) { + tapAndDrag.addPointer(down5); + tester.closeArena(5); + tester.route(down5); + tester.route(move5); + tester.route(up5); + GestureBinding.instance.gestureArena.sweep(5); + expect(events, ['down#1', 'dragstart#1', 'dragend#1']); + + events.clear(); + tester.async.elapse(const Duration(milliseconds: 1000)); + tapAndDrag.addPointer(down1); + tester.closeArena(1); + tester.route(down1); + tester.route(up1); + GestureBinding.instance.gestureArena.sweep(1); + expect(events, ['down#1', 'up#1']); + }); + + testGesture('Fires cancel and resets for PointerCancelEvent', (GestureTester tester) { + tapAndDrag.addPointer(down1); + tester.closeArena(1); + tester.route(down1); + tester.route(cancel1); + GestureBinding.instance.gestureArena.sweep(1); + expect(events, ['down#1', 'cancel']); + + events.clear(); + tester.async.elapse(const Duration(milliseconds: 100)); + tapAndDrag.addPointer(down2); + tester.closeArena(2); + tester.route(down2); + tester.route(up2); + GestureBinding.instance.gestureArena.sweep(2); + expect(events, ['down#1', 'up#1']); + }); +} diff --git a/packages/flutter/test/widgets/text_selection_test.dart b/packages/flutter/test/widgets/text_selection_test.dart index f8508114870e2..034b3418854d0 100644 --- a/packages/flutter/test/widgets/text_selection_test.dart +++ b/packages/flutter/test/widgets/text_selection_test.dart @@ -25,16 +25,16 @@ void main() { late int dragEndCount; const Offset forcePressOffset = Offset(400.0, 50.0); - void handleTapDown(TapDownDetails details) { tapCount++; } - void handleSingleTapUp(TapUpDetails details) { singleTapUpCount++; } + void handleTapDown(TapDragDownDetails details) { tapCount++; } + void handleSingleTapUp(TapDragUpDetails details) { singleTapUpCount++; } void handleSingleTapCancel() { singleTapCancelCount++; } void handleSingleLongTapStart(LongPressStartDetails details) { singleLongTapStartCount++; } - void handleDoubleTapDown(TapDownDetails details) { doubleTapDownCount++; } + void handleDoubleTapDown(TapDragDownDetails details) { doubleTapDownCount++; } void handleForcePressStart(ForcePressDetails details) { forcePressStartCount++; } void handleForcePressEnd(ForcePressDetails details) { forcePressEndCount++; } - void handleDragSelectionStart(DragStartDetails details) { dragStartCount++; } - void handleDragSelectionUpdate(DragStartDetails _, DragUpdateDetails details) { dragUpdateCount++; } - void handleDragSelectionEnd(DragEndDetails details) { dragEndCount++; } + void handleDragSelectionStart(TapDragStartDetails details) { dragStartCount++; } + void handleDragSelectionUpdate(TapDragUpdateDetails details) { dragUpdateCount++; } + void handleDragSelectionEnd(TapDragEndDetails details) { dragEndCount++; } setUp(() { tapCount = 0; @@ -173,7 +173,12 @@ void main() { await gesture.moveBy(const Offset(100, 100)); await tester.pump(); expect(singleTapUpCount, 0); - expect(tapCount, 0); + // Before the move to TapAndDragGestureRecognizer the tapCount was 0 because the + // TapGestureRecognizer rejected itself when the initial pointer moved past a certain + // threshold. With TapAndDragGestureRecognizer, we have two thresholds, a normal tap + // threshold, and a drag threshold, so it is possible for the tap count to increase + // even though the original pointer has moved beyond the tap threshold. + expect(tapCount, 1); expect(singleTapCancelCount, 0); expect(doubleTapDownCount, 0); expect(singleLongTapStartCount, 0); @@ -181,7 +186,7 @@ void main() { await gesture.up(); // Nothing else happens on up. expect(singleTapUpCount, 0); - expect(tapCount, 0); + expect(tapCount, 1); expect(singleTapCancelCount, 0); expect(doubleTapDownCount, 0); expect(singleLongTapStartCount, 0); @@ -195,7 +200,7 @@ void main() { await tester.pump(); expect(singleTapUpCount, 0); expect(tapCount, 1); - expect(singleTapCancelCount, 1); + expect(singleTapCancelCount, 0); expect(doubleTapDownCount, 0); expect(singleLongTapStartCount, 0); }); @@ -370,7 +375,7 @@ void main() { expect(singleLongTapStartCount, 0); }); - testWidgets('a touch drag is not recognized for text selection', (WidgetTester tester) async { + testWidgets('a touch drag is recognized for text selection', (WidgetTester tester) async { await pumpGestureDetector(tester); final int pointerValue = tester.nextPointer; @@ -384,11 +389,12 @@ void main() { await gesture.up(); await tester.pumpAndSettle(); - expect(tapCount, 0); + expect(tapCount, 1); expect(singleTapUpCount, 0); - expect(dragStartCount, 0); - expect(dragUpdateCount, 0); - expect(dragEndCount, 0); + expect(singleTapCancelCount, 0); + expect(dragStartCount, 1); + expect(dragUpdateCount, 1); + expect(dragEndCount, 1); }); testWidgets('a mouse drag is recognized for text selection', (WidgetTester tester) async { @@ -406,8 +412,11 @@ void main() { await gesture.up(); await tester.pumpAndSettle(); - expect(tapCount, 0); + // The tap and drag gesture recognizer will detect the tap down, but not the tap up. + expect(tapCount, 1); + expect(singleTapCancelCount, 0); expect(singleTapUpCount, 0); + expect(dragStartCount, 1); expect(dragUpdateCount, 1); expect(dragEndCount, 1); @@ -428,6 +437,11 @@ void main() { await gesture.up(); await tester.pumpAndSettle(); + // The tap and drag gesture recognizer will detect the tap down, but not the tap up. + expect(tapCount, 1); + expect(singleTapCancelCount, 0); + expect(singleTapUpCount, 0); + expect(dragStartCount, 1); expect(dragUpdateCount, 1); expect(dragEndCount, 1); @@ -746,12 +760,18 @@ void main() { final Offset position = textOffsetToPosition(tester, 4); await tester.tapAt(position); - await tester.pump(); + // Don't do a double tap drag. + await tester.pump(const Duration(milliseconds: 300)); expect(controller.selection.isCollapsed, isTrue); expect(controller.selection.baseOffset, 4); final TestGesture gesture = await tester.startGesture(position, kind: PointerDeviceKind.mouse); + + // Checking that double-tap was not registered. + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 4); + addTearDown(gesture.removePointer); await tester.pump(); await gesture.moveTo(textOffsetToPosition(tester, 7)); From 63653e827299938be8821e8c17bd17e6e869864c Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 3 Jan 2023 17:13:21 -0800 Subject: [PATCH 14/18] == override parameters are non-nullable (#117839) --- packages/flutter/lib/src/material/about.dart | 2 +- packages/flutter/test/rendering/mouse_tracker_cursor_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/material/about.dart b/packages/flutter/lib/src/material/about.dart index b6ea5f91020d1..a2c808b862322 100644 --- a/packages/flutter/lib/src/material/about.dart +++ b/packages/flutter/lib/src/material/about.dart @@ -756,7 +756,7 @@ class _DetailArguments { final List licenseEntries; @override - bool operator ==(final dynamic other) { + bool operator ==(final Object other) { if (other is _DetailArguments) { return other.packageName == packageName; } diff --git a/packages/flutter/test/rendering/mouse_tracker_cursor_test.dart b/packages/flutter/test/rendering/mouse_tracker_cursor_test.dart index 739e80cd96308..7829d697289b7 100644 --- a/packages/flutter/test/rendering/mouse_tracker_cursor_test.dart +++ b/packages/flutter/test/rendering/mouse_tracker_cursor_test.dart @@ -462,7 +462,7 @@ class _CursorUpdateDetails extends MethodCall { Map get arguments => super.arguments as Map; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(other, this)) { return true; } From 906761cf9b9a3ded04a3dc09acfb92cecf0dd7fa Mon Sep 17 00:00:00 2001 From: Peixin Li Date: Tue, 3 Jan 2023 18:48:00 -0800 Subject: [PATCH 15/18] Fix the message strings for xcodeMissing and xcodeIncomplete (#117922) * Add macOS to xcodeMissing and xcodeIncomplete * And unit test --- packages/flutter_tools/lib/src/base/user_messages.dart | 4 ++-- .../test/general.shard/base/user_messages_test.dart | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart index c3f4a69173cf5..d8236e3c33e80 100644 --- a/packages/flutter_tools/lib/src/base/user_messages.dart +++ b/packages/flutter_tools/lib/src/base/user_messages.dart @@ -176,10 +176,10 @@ class UserMessages { 'Launch Xcode and install additional required components when prompted or run:\n' ' sudo xcodebuild -runFirstLaunch'; String get xcodeMissing => - 'Xcode not installed; this is necessary for iOS development.\n' + 'Xcode not installed; this is necessary for iOS and macOS development.\n' 'Download at https://developer.apple.com/xcode/download/.'; String get xcodeIncomplete => - 'Xcode installation is incomplete; a full installation is necessary for iOS development.\n' + 'Xcode installation is incomplete; a full installation is necessary for iOS and macOS development.\n' 'Download at: https://developer.apple.com/xcode/download/\n' 'Or install Xcode via the App Store.\n' 'Once installed, run:\n' diff --git a/packages/flutter_tools/test/general.shard/base/user_messages_test.dart b/packages/flutter_tools/test/general.shard/base/user_messages_test.dart index 862fb2a08e0cc..a001bbf4b460b 100644 --- a/packages/flutter_tools/test/general.shard/base/user_messages_test.dart +++ b/packages/flutter_tools/test/general.shard/base/user_messages_test.dart @@ -30,4 +30,10 @@ void main() { checkInstallationURL((Platform platform) => userMessages.androidSdkBuildToolsOutdated(0, '', platform)); checkInstallationURL((Platform platform) => userMessages.androidStudioInstallation(platform)); }); + + testWithoutContext('Xcode installation instructions', () { + final UserMessages userMessages = UserMessages(); + expect(userMessages.xcodeMissing, contains('iOS and macOS')); + expect(userMessages.xcodeIncomplete, contains('iOS and macOS')); + }); } From e599e5c9a973249c2b8af96b8958726f2e2b94f2 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 3 Jan 2023 22:32:12 -0500 Subject: [PATCH 16/18] 32c468507 Roll quiver to 3.2.1 (flutter/engine#38617) (#117942) --- bin/internal/engine.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 307c25f9942b2..570f0520bc86f 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -2213b80dd964f09ed087b4b284490af7b7e4e82a +32c468507b328fc0db2175e35054ba4475253f8c From c53501d83562bc983e1e8ff31e1e8eaa474f2049 Mon Sep 17 00:00:00 2001 From: Callum Moffat Date: Tue, 3 Jan 2023 23:00:08 -0500 Subject: [PATCH 17/18] Send text direction in selection rects (#117436) --- .../flutter/lib/src/rendering/editable.dart | 11 +++++++--- .../flutter/lib/src/services/text_input.dart | 21 ++++++++++++++++--- .../lib/src/widgets/editable_text.dart | 12 +++++------ .../test/services/text_input_test.dart | 12 ++++++++++- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index d6fad8c988b20..e99b0825338c7 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -1316,11 +1316,16 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, /// Returns a list of rects that bound the given selection. /// /// See [TextPainter.getBoxesForSelection] for more details. - List getBoxesForSelection(TextSelection selection) { + List getBoxesForSelection(TextSelection selection) { _computeTextMetricsIfNeeded(); return _textPainter.getBoxesForSelection(selection) - .map((TextBox textBox) => textBox.toRect().shift(_paintOffset)) - .toList(); + .map((TextBox textBox) => TextBox.fromLTRBD( + textBox.left + _paintOffset.dx, + textBox.top + _paintOffset.dy, + textBox.right + _paintOffset.dx, + textBox.bottom + _paintOffset.dy, + textBox.direction + )).toList(); } @override diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index b2ccef4320b65..3506da6a1b006 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -1211,7 +1211,11 @@ abstract class ScribbleClient { class SelectionRect { /// Constructor for creating a [SelectionRect] from a text [position] and /// [bounds]. - const SelectionRect({required this.position, required this.bounds}); + const SelectionRect({ + required this.position, + required this.bounds, + this.direction = TextDirection.ltr, + }); /// The position of this selection rect within the text String. final int position; @@ -1220,6 +1224,9 @@ class SelectionRect { /// currently focused [RenderEditable]'s coordinate space. final Rect bounds; + /// The direction text flows within this selection rect. + final TextDirection direction; + @override bool operator ==(Object other) { if (identical(this, other)) { @@ -1230,7 +1237,8 @@ class SelectionRect { } return other is SelectionRect && other.position == position - && other.bounds == bounds; + && other.bounds == bounds + && other.direction == direction; } @override @@ -2321,7 +2329,14 @@ class _PlatformTextInputControl with TextInputControl { _channel.invokeMethod( 'TextInput.setSelectionRects', selectionRects.map((SelectionRect rect) { - return [rect.bounds.left, rect.bounds.top, rect.bounds.width, rect.bounds.height, rect.position]; + return [ + rect.bounds.left, + rect.bounds.top, + rect.bounds.width, + rect.bounds.height, + rect.position, + rect.direction.index, + ]; }).toList(), ); } diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index f5698a1a67d4b..1d83b559e6e6d 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -3279,7 +3279,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien if (selection.isCollapsed) { rectToReveal = targetOffset.rect; } else { - final List selectionBoxes = renderEditable.getBoxesForSelection(selection); + final List selectionBoxes = renderEditable.getBoxesForSelection(selection); // selectionBoxes may be empty if, for example, the selection does not // encompass a full character, like if it only contained part of an // extended grapheme cluster. @@ -3287,7 +3287,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien rectToReveal = targetOffset.rect; } else { rectToReveal = selection.baseOffset < selection.extentOffset ? - selectionBoxes.last : selectionBoxes.first; + selectionBoxes.last.toRect() : selectionBoxes.first.toRect(); } } @@ -3590,11 +3590,11 @@ class EditableTextState extends State with AutomaticKeepAliveClien final CharacterRange characterRange = CharacterRange(plainText); while (characterRange.moveNext()) { final int graphemeEnd = graphemeStart + characterRange.current.length; - final List boxes = renderEditable.getBoxesForSelection( + final List boxes = renderEditable.getBoxesForSelection( TextSelection(baseOffset: graphemeStart, extentOffset: graphemeEnd), ); - final Rect? box = boxes.isEmpty ? null : boxes.first; + final TextBox? box = boxes.isEmpty ? null : boxes.first; if (box != null) { final Rect paintBounds = renderEditable.paintBounds; // Stop early when characters are already below the bottom edge of the @@ -3602,8 +3602,8 @@ class EditableTextState extends State with AutomaticKeepAliveClien if (paintBounds.bottom <= box.top) { break; } - if (paintBounds.contains(box.topLeft) || paintBounds.contains(box.bottomRight)) { - rects.add(SelectionRect(position: graphemeStart, bounds: box)); + if (paintBounds.contains(Offset(box.left, box.top)) || paintBounds.contains(Offset(box.right, box.bottom))) { + rects.add(SelectionRect(position: graphemeStart, bounds: box.toRect(), direction: box.direction)); } } graphemeStart = graphemeEnd; diff --git a/packages/flutter/test/services/text_input_test.dart b/packages/flutter/test/services/text_input_test.dart index a1be567feb1f5..254e7b714452f 100644 --- a/packages/flutter/test/services/text_input_test.dart +++ b/packages/flutter/test/services/text_input_test.dart @@ -906,10 +906,20 @@ void main() { expect(fakeTextChannel.outgoingCalls.length, 6); expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.setEditableSizeAndTransform'); - connection.setSelectionRects(const [SelectionRect(position: 0, bounds: Rect.zero)]); + connection.setSelectionRects(const [SelectionRect(position: 1, bounds: Rect.fromLTWH(2, 3, 4, 5), direction: TextDirection.rtl)]); expectedMethodCalls.add('setSelectionRects'); expect(control.methodCalls, expectedMethodCalls); expect(fakeTextChannel.outgoingCalls.length, 7); + expect(fakeTextChannel.outgoingCalls.last.arguments, const TypeMatcher>>()); + final List> sentList = fakeTextChannel.outgoingCalls.last.arguments as List>; + expect(sentList.length, 1); + expect(sentList[0].length, 6); + expect(sentList[0][0], 2); // left + expect(sentList[0][1], 3); // top + expect(sentList[0][2], 4); // width + expect(sentList[0][3], 5); // height + expect(sentList[0][4], 1); // position + expect(sentList[0][5], TextDirection.rtl.index); // direction expect(fakeTextChannel.outgoingCalls.last.method, 'TextInput.setSelectionRects'); connection.setStyle( From 025ce117b7eda83a1402c2b74372a290cfa6da4e Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Wed, 4 Jan 2023 10:54:21 +0100 Subject: [PATCH 18/18] Correctly propagate verbosity to subtasks in flutter.gradle (#117897) * Correctly propagate verbosity to subtasks in flutter.gradle * Add test * Revert accidental changes * Fix copyright year * Fix imports --- packages/flutter_tools/gradle/flutter.gradle | 27 ++++----- .../flutter_build_apk_verbose_test.dart | 55 +++++++++++++++++++ 2 files changed, 67 insertions(+), 15 deletions(-) create mode 100644 packages/flutter_tools/test/integration.shard/flutter_build_apk_verbose_test.dart diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle index e2e329217c4ed..be7c89804cec4 100644 --- a/packages/flutter_tools/gradle/flutter.gradle +++ b/packages/flutter_tools/gradle/flutter.gradle @@ -618,10 +618,7 @@ class FlutterPlugin implements Plugin { } private Boolean shouldSplitPerAbi() { - if (project.hasProperty('split-per-abi')) { - return project.property('split-per-abi').toBoolean() - } - return false; + return project.findProperty('split-per-abi')?.toBoolean() ?: false; } private Boolean useLocalEngine() { @@ -629,18 +626,12 @@ class FlutterPlugin implements Plugin { } private Boolean isVerbose() { - if (project.hasProperty('verbose')) { - return project.property('verbose').toBoolean() - } - return false + return project.findProperty('verbose')?.toBoolean() ?: false; } /** Whether to build the debug app in "fast-start" mode. */ private Boolean isFastStart() { - if (project.hasProperty("fast-start")) { - return project.property("fast-start").toBoolean() - } - return false + return project.findProperty("fast-start")?.toBoolean() ?: false; } private static Boolean isBuiltAsApp(Project project) { @@ -877,6 +868,12 @@ class FlutterPlugin implements Plugin { } String variantBuildMode = buildModeFor(variant.buildType) String taskName = toCamelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name]) + // Be careful when configuring task below, Groovy has bizarre + // scoping rules: writing `verbose isVerbose()` means calling + // `isVerbose` on the task itself - which would return `verbose` + // original value. You either need to hoist the value + // into a separate variable `verbose verboseValue` or prefix with + // `this` (`verbose this.isVerbose()`). FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) { flutterRoot this.flutterRoot flutterExecutable this.flutterExecutable @@ -884,8 +881,8 @@ class FlutterPlugin implements Plugin { localEngine this.localEngine localEngineSrcPath this.localEngineSrcPath targetPath getFlutterTarget() - verbose isVerbose() - fastStart isFastStart() + verbose this.isVerbose() + fastStart this.isFastStart() fileSystemRoots fileSystemRootsValue fileSystemScheme fileSystemSchemeValue trackWidgetCreation trackWidgetCreationValue @@ -1089,7 +1086,7 @@ abstract class BaseFlutterTask extends DefaultTask { Boolean fastStart @Input String targetPath - @Optional @Internal + @Optional @Input Boolean verbose @Optional @Input String[] fileSystemRoots diff --git a/packages/flutter_tools/test/integration.shard/flutter_build_apk_verbose_test.dart b/packages/flutter_tools/test/integration.shard/flutter_build_apk_verbose_test.dart new file mode 100644 index 0000000000000..cff5e463b09dc --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/flutter_build_apk_verbose_test.dart @@ -0,0 +1,55 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; + +import '../src/common.dart'; +import 'test_utils.dart'; + +// Test that verbosity it propagated to Gradle tasks correctly. +void main() { + late Directory tempDir; + late String flutterBin; + late Directory exampleAppDir; + + setUp(() async { + tempDir = createResolvedTempDirectorySync('flutter_build_test.'); + flutterBin = fileSystem.path.join( + getFlutterRoot(), + 'bin', + 'flutter', + ); + exampleAppDir = tempDir.childDirectory('aaa').childDirectory('example'); + + processManager.runSync([ + flutterBin, + ...getLocalEngineArguments(), + 'create', + '--template=plugin', + '--platforms=android', + 'aaa', + ], workingDirectory: tempDir.path); + }); + + tearDown(() async { + tryToDelete(tempDir); + }); + + test( + 'flutter build apk -v output should contain gen_snapshot command', + () async { + final ProcessResult result = processManager.runSync([ + flutterBin, + ...getLocalEngineArguments(), + 'build', + 'apk', + '--target-platform=android-arm', + '-v', + ], workingDirectory: exampleAppDir.path); + expect( + result.stdout, contains(RegExp(r'executing:\s+\S+gen_snapshot\s+'))); + }, + ); +}